Skip to content

Commit

Permalink
[Feature] Voice channel status support (#2777)
Browse files Browse the repository at this point in the history
* initial commit

* MOCKED CHANNELS AGRHHHHHHHH

* fix for sharded

* yup
  • Loading branch information
Misha-133 committed Nov 18, 2023
1 parent 8e4d449 commit 8060dcf
Show file tree
Hide file tree
Showing 30 changed files with 323 additions and 17 deletions.
5 changes: 5 additions & 0 deletions src/Discord.Net.Core/DiscordConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ public class DiscordConfig
/// </summary>
public const int MaxApplicationTagCount = 5;

/// <summary>
/// Returns the maximum length of a voice channel status.
/// </summary>
public const int MaxVoiceChannelStatusLength = 500;

/// <summary>
/// Returns the maximum number of entitlements that can be gotten per-batch.
/// </summary>
Expand Down
12 changes: 11 additions & 1 deletion src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ public enum ActionType
/// <summary>
/// Guild Onboarding was updated.
/// </summary>
OnboardingUpdated = 167
OnboardingUpdated = 167,

/// <summary>
/// A voice channel status was updated by a user.
/// </summary>
VoiceChannelStatusUpdated = 192,

/// <summary>
/// A voice channel status was deleted by a user.
/// </summary>
VoiceChannelStatusDeleted = 193
}
}
10 changes: 10 additions & 0 deletions src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,15 @@ public interface IVoiceChannel : ITextChannel, IAudioChannel
/// </returns>
/// <seealso cref="VoiceChannelProperties"/>
Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null);

/// <summary>
/// Sets the voice channel status in the current channel.
/// </summary>
/// <param name="status">The string to set as status.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task SetStatusAsync(string status, RequestOptions options = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,10 @@ public enum ChannelPermission : ulong
/// Allows sending voice messages.
/// </summary>
SendVoiceMessages = 1L << 46,

/// <summary>
/// Allows setting voice channel status.
/// </summary>
SetVoiceChannelStatus = 1L << 48,
}
}
20 changes: 14 additions & 6 deletions src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct ChannelPermissions
/// <summary>
/// Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels.
/// </summary>
public static readonly ChannelPermissions Voice = new(0b10001_001010_001010_110011_111101_111111_111101_010001);
public static readonly ChannelPermissions Voice = new(0b1_010001_001010_001010_110011_111101_111111_111101_010001);

/// <summary>
/// Gets a <see cref="ChannelPermissions"/> that grants all permissions for stage channels.
Expand Down Expand Up @@ -142,6 +142,9 @@ public static ChannelPermissions All(IChannel channel)
public bool CreateEvents => Permissions.GetValue(RawValue, ChannelPermission.CreateEvents);
/// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary>
public bool SendVoiceMessages => Permissions.GetValue(RawValue, ChannelPermission.SendVoiceMessages);
/// <summary> If <see langword="true"/>, a user can set the status of a voice channel.</summary>
public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus);


/// <summary> Creates a new <see cref="ChannelPermissions"/> with the provided packed value.</summary>
public ChannelPermissions(ulong rawValue) { RawValue = rawValue; }
Expand Down Expand Up @@ -179,7 +182,8 @@ public static ChannelPermissions All(IChannel channel)
bool? startEmbeddedActivities = null,
bool? useSoundboard = null,
bool? createEvents = null,
bool? sendVoiceMessages = null)
bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
{
ulong value = initialValue;

Expand Down Expand Up @@ -216,6 +220,7 @@ public static ChannelPermissions All(IChannel channel)
Permissions.SetValue(ref value, useSoundboard, ChannelPermission.UseSoundboard);
Permissions.SetValue(ref value, createEvents, ChannelPermission.CreateEvents);
Permissions.SetValue(ref value, sendVoiceMessages, ChannelPermission.SendVoiceMessages);
Permissions.SetValue(ref value, setVoiceChannelStatus, ChannelPermission.SetVoiceChannelStatus);

RawValue = value;
}
Expand Down Expand Up @@ -254,12 +259,13 @@ public static ChannelPermissions All(IChannel channel)
bool startEmbeddedActivities = false,
bool useSoundboard = false,
bool createEvents = false,
bool sendVoiceMessages = false)
bool sendVoiceMessages = false,
bool setVoiceChannelStatus = false)
: this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect,
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks,
useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages)
startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages, setVoiceChannelStatus)
{ }

/// <summary> Creates a new <see cref="ChannelPermissions"/> from this one, changing the provided non-null permissions.</summary>
Expand Down Expand Up @@ -296,7 +302,8 @@ public static ChannelPermissions All(IChannel channel)
bool? startEmbeddedActivities = null,
bool? useSoundboard = null,
bool? createEvents = null,
bool? sendVoiceMessages = null)
bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
=> new ChannelPermissions(RawValue,
createInstantInvite,
manageChannel,
Expand Down Expand Up @@ -330,7 +337,8 @@ public static ChannelPermissions All(IChannel channel)
startEmbeddedActivities,
useSoundboard,
createEvents,
sendVoiceMessages);
sendVoiceMessages,
setVoiceChannelStatus);

public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission);

Expand Down
5 changes: 5 additions & 0 deletions src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,5 +267,10 @@ public enum GuildPermission : ulong
/// Allows sending voice messages.
/// </summary>
SendVoiceMessages = 1L << 46,

/// <summary>
/// Allows setting voice channel status.
/// </summary>
SetVoiceChannelStatus = 1L << 48,
}
}
17 changes: 12 additions & 5 deletions src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public struct GuildPermissions
public bool ViewMonetizationAnalytics => Permissions.GetValue(RawValue, GuildPermission.ViewMonetizationAnalytics);
/// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary>
public bool SendVoiceMessages => Permissions.GetValue(RawValue, GuildPermission.SendVoiceMessages);
/// <summary> If <see langword="true"/>, a user can set the status of a voice channel.</summary>
public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus);

/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; }
Expand Down Expand Up @@ -161,7 +163,8 @@ public struct GuildPermissions
bool? moderateMembers = null,
bool? useSoundboard = null,
bool? viewMonetizationAnalytics = null,
bool? sendVoiceMessages = null)
bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
{
ulong value = initialValue;

Expand Down Expand Up @@ -209,6 +212,7 @@ public struct GuildPermissions
Permissions.SetValue(ref value, useSoundboard, GuildPermission.UseSoundboard);
Permissions.SetValue(ref value, viewMonetizationAnalytics, GuildPermission.ViewMonetizationAnalytics);
Permissions.SetValue(ref value, sendVoiceMessages, GuildPermission.SendVoiceMessages);
Permissions.SetValue(ref value, setVoiceChannelStatus, GuildPermission.SetVoiceChannelStatus);

RawValue = value;
}
Expand Down Expand Up @@ -258,7 +262,8 @@ public struct GuildPermissions
bool moderateMembers = false,
bool useSoundboard = false,
bool viewMonetizationAnalytics = false,
bool sendVoiceMessages = false)
bool sendVoiceMessages = false,
bool setVoiceChannelStatus = false)
: this(0,
createInstantInvite: createInstantInvite,
manageRoles: manageRoles,
Expand Down Expand Up @@ -303,7 +308,8 @@ public struct GuildPermissions
moderateMembers: moderateMembers,
useSoundboard: useSoundboard,
viewMonetizationAnalytics: viewMonetizationAnalytics,
sendVoiceMessages: sendVoiceMessages)
sendVoiceMessages: sendVoiceMessages,
setVoiceChannelStatus: setVoiceChannelStatus)
{ }

/// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>
Expand Down Expand Up @@ -351,13 +357,14 @@ public struct GuildPermissions
bool? moderateMembers = null,
bool? useSoundboard = null,
bool? viewMonetizationAnalytics = null,
bool? sendVoiceMessages = null)
bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = 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, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages);
startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages, setVoiceChannelStatus);

/// <summary>
/// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled
Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.Rest/API/Common/AuditLogOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ internal class AuditLogOptions

[JsonProperty("auto_moderation_rule_trigger_type")]
public AutoModTriggerType? AutoModRuleTriggerType { get; set; }

[JsonProperty("status")]
public string Status { get; set; }
}
3 changes: 3 additions & 0 deletions src/Discord.Net.Rest/API/Common/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ internal class Channel
[JsonProperty("video_quality_mode")]
public Optional<VideoQualityMode> VideoQualityMode { get; set; }

[JsonProperty("status")]
public Optional<string> Status { get; set; }

//PrivateChannel
[JsonProperty("recipients")]
public Optional<User[]> Recipients { get; set; }
Expand Down
9 changes: 9 additions & 0 deletions src/Discord.Net.Rest/API/Rest/ModifyVoiceStatusParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Newtonsoft.Json;

namespace Discord.API.Rest;

internal class ModifyVoiceStatusParams
{
[JsonProperty("status")]
public string Status { get; set; }
}
11 changes: 11 additions & 0 deletions src/Discord.Net.Rest/DiscordRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,17 @@ public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable<Rest.Modif
break;
}
}

public async Task ModifyVoiceChannelStatusAsync(ulong channelId, string status, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

var payload = new ModifyVoiceStatusParams { Status = status };
var ids = new BucketIds();

await SendJsonAsync("PUT", () => $"channels/{channelId}/voice-status", payload, ids, options: options);
}

#endregion

#region Threads
Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ internal static class AuditLogHelper
[ActionType.OnboardingQuestionCreated] = OnboardingPromptCreatedAuditLogData.Create,
[ActionType.OnboardingQuestionUpdated] = OnboardingPromptUpdatedAuditLogData.Create,
[ActionType.OnboardingUpdated] = OnboardingUpdatedAuditLogData.Create,

[ActionType.VoiceChannelStatusUpdated] = VoiceChannelStatusUpdateAuditLogData.Create,
[ActionType.VoiceChannelStatusDeleted] = VoiceChannelStatusDeletedAuditLogData.Create
};

public static IAuditLogData CreateData(BaseDiscordClient discord, EntryModel entry, Model log = null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;

namespace Discord.Rest;

/// <summary>
/// Contains a piece of audit log data related to a voice channel status delete.
/// </summary>
public class VoiceChannelStatusDeletedAuditLogData : IAuditLogData
{
private VoiceChannelStatusDeletedAuditLogData(ulong channelId)
{
ChannelId = channelId;
}

internal static VoiceChannelStatusDeletedAuditLogData Create(BaseDiscordClient discord, EntryModel entry, Model log = null)
{
return new (entry.TargetId!.Value);
}

/// <summary>
/// Get the id of the channel status was removed in.
/// </summary>
public ulong ChannelId { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;

namespace Discord.Rest;

/// <summary>
/// Contains a piece of audit log data related to a voice channel status update.
/// </summary>
public class VoiceChannelStatusUpdateAuditLogData : IAuditLogData
{
private VoiceChannelStatusUpdateAuditLogData(string status, ulong channelId)
{
Status = status;
ChannelId = channelId;
}

internal static VoiceChannelStatusUpdateAuditLogData Create(BaseDiscordClient discord, EntryModel entry, Model log = null)
{
return new (entry.Options.Status, entry.TargetId!.Value);
}

/// <summary>
/// Gets the status that was set in the voice channel.
/// </summary>
public string Status { get; }

/// <summary>
/// Get the id of the channel status was updated in.
/// </summary>
public ulong ChannelId { get; }
}
11 changes: 11 additions & 0 deletions src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,5 +636,16 @@ public static async Task SyncPermissionsAsync(INestedChannel channel, BaseDiscor
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
#endregion

#region Voice

public static async Task ModifyVoiceChannelStatusAsync(IVoiceChannel channel, string status, BaseDiscordClient client, RequestOptions options)
{
Preconditions.AtMost(status.Length, DiscordConfig.MaxVoiceChannelStatusLength, $"Voice channel status length must be less than {DiscordConfig.MaxVoiceChannelStatusLength}.");

await client.ApiClient.ModifyVoiceChannelStatusAsync(channel.Id, status, options).ConfigureAwait(false);
}

#endregion
}
}
8 changes: 8 additions & 0 deletions src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,13 @@ public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = nul

return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args);
}

/// <inheritdoc />
/// <remarks>
/// Setting voice channel status is not supported in stage channels.
/// </remarks>
/// <exception cref="NotSupportedException">Setting voice channel status is not supported in stage channels.</exception>
public override Task SetStatusAsync(string status, RequestOptions options = null)
=> throw new NotSupportedException("Setting voice channel status is not supported in stage channels.");
}
}
4 changes: 4 additions & 0 deletions src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public async Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOption
public override Task<RestThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
=> throw new InvalidOperationException("Cannot create a thread within a voice channel");

/// <inheritdoc />
public virtual Task SetStatusAsync(string status, RequestOptions options = null)
=> ChannelHelper.ModifyVoiceChannelStatusAsync(this, status, Discord, options);

#endregion

private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Newtonsoft.Json;

namespace Discord.API.Gateway;

internal class VoiceChannelStatusUpdateEvent
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("guild_id")]
public ulong GuildId { get; set; }

[JsonProperty("status")]
public string Status { get; set; }
}
16 changes: 16 additions & 0 deletions src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ public partial class BaseSocketClient
remove { _channelUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();

/// <summary>
/// Fired when status of a voice channel is updated.
/// </summary>
/// <remarks>
/// The previous state of the channel is passed into the first parameter; the updated channel is passed into the second one.
/// </remarks>
public event Func<Cacheable<SocketVoiceChannel, ulong>, string, string, Task> VoiceChannelStatusUpdated
{
add { _voiceChannelStatusUpdated.Add(value); }
remove { _voiceChannelStatusUpdated.Remove(value); }
}

internal readonly AsyncEvent<Func<Cacheable<SocketVoiceChannel, ulong>, string, string, Task>> _voiceChannelStatusUpdated = new();


#endregion

#region Messages
Expand Down

0 comments on commit 8060dcf

Please sign in to comment.