Browse Source

Guild feature revamp and smart gateway intent checks

pull/1923/head
quin lynch 3 years ago
parent
commit
bceeab2d0c
11 changed files with 247 additions and 23 deletions
  1. +105
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
  2. +46
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs
  3. +3
    -3
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  4. +1
    -1
      src/Discord.Net.Rest/API/Common/Guild.cs
  5. +4
    -3
      src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
  6. +3
    -8
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  7. +6
    -0
      src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs
  8. +2
    -0
      src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
  9. +60
    -0
      src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs
  10. +14
    -0
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  11. +3
    -8
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs

+ 105
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
[Flags]
public enum GuildFeature
{
/// <summary>
/// The guild has no features.
/// </summary>
None = 0,
/// <summary>
/// The guild has access to set an animated guild icon.
/// </summary>
AnimatedIcon = 1 << 0,
/// <summary>
/// The guild has access to set a guild banner image.
/// </summary>
Banner = 1 << 1,
/// <summary>
/// The guild has access to use commerce features (i.e. create store channels).
/// </summary>
Commerce = 1 << 2,
/// <summary>
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates.
/// </summary>
Community = 1 << 3,
/// <summary>
/// The guild is able to be discovered in the directory.
/// </summary>
Discoverable = 1 << 4,
/// <summary>
/// The guild is able to be featured in the directory.
/// </summary>
Featureable = 1 << 5,
/// <summary>
/// The guild has access to set an invite splash background.
/// </summary>
InviteSplash = 1 << 6,
/// <summary>
/// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>.
/// </summary>
MemberVerificationGateEnabled = 1 << 7,
/// <summary>
/// The guild has enabled monetization.
/// </summary>
MonetizationEnabled = 1 << 8,
/// <summary>
/// The guild has increased custom sticker slots.
/// </summary>
MoreStickers = 1 << 9,
/// <summary>
/// The guild has access to create news channels.
/// </summary>
News = 1 << 10,
/// <summary>
/// The guild is partnered.
/// </summary>
Partnered = 1 << 11,
/// <summary>
/// The guild can be previewed before joining via Membership Screening or the directory.
/// </summary>
PreviewEnabled = 1 << 12,
/// <summary>
/// The guild has access to create private threads.
/// </summary>
PrivateThreads = 1 << 13,
/// <summary>
/// The guild is able to set role icons.
/// </summary>
RoleIcons = 1 << 14,
/// <summary>
/// The guild has access to the seven day archive time for threads.
/// </summary>
SevenDayThreadArchive = 1 << 15,
/// <summary>
/// The guild has access to the three day archive time for threads.
/// </summary>
ThreeDayThreadArchive = 1 << 16,
/// <summary>
/// The guild has enabled ticketed events.
/// </summary>
TicketedEventsEnabled = 1 << 17,
/// <summary>
/// The guild has access to set a vanity URL.
/// </summary>
VanityUrl = 1 << 18,
/// <summary>
/// The guild is verified.
/// </summary>
Verified = 1 << 19,
/// <summary>
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
/// </summary>
VIPRegions = 1 << 20,
/// <summary>
/// The guild has enabled the welcome screen.
/// </summary>
WelcomeScreenEnabled = 1 << 21,
}
}

+ 46
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public class GuildFeatures
{
/// <summary>
/// Gets the flags of recognized features for this guild.
/// </summary>
public GuildFeature Value { get; }

/// <summary>
/// Gets a collection of experimental features for this guild.
/// </summary>
public IReadOnlyCollection<string> Experimental { get; }


internal GuildFeatures(GuildFeature value, string[] experimental)
{
Value = value;
Experimental = experimental.ToImmutableArray();
}

public bool HasFeature(GuildFeature feature)
=> Value.HasFlag(feature);
public bool HasFeature(string feature)
=> Experimental.Contains(feature);

internal void EnsureFeature(GuildFeature feature)
{
if (!HasFeature(feature))
{
var vals = Enum.GetValues(typeof(GuildFeature)).Cast<GuildFeature>();

var missingValues = vals.Where(x => feature.HasFlag(x) && !Value.HasFlag(x));

throw new InvalidOperationException($"Missing required guild feature{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation.");
}
}
}
}

+ 3
- 3
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -207,12 +207,12 @@ namespace Discord
/// </returns>
IReadOnlyCollection<ICustomSticker> Stickers { get; }
/// <summary>
/// Gets a collection of all extra features added to this guild.
/// Gets the features for this guild.
/// </summary>
/// <returns>
/// A read-only collection of enabled features in this guild.
/// A flags enum containing all the features for the guild.
/// </returns>
IReadOnlyCollection<string> Features { get; }
GuildFeatures Features { get; }
/// <summary>
/// Gets a collection of all roles in this guild.
/// </summary>


+ 1
- 1
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -35,7 +35,7 @@ namespace Discord.API
[JsonProperty("emojis")]
public Emoji[] Emojis { get; set; }
[JsonProperty("features")]
public string[] Features { get; set; }
public GuildFeatures Features { get; set; }
[JsonProperty("mfa_level")]
public MfaLevel MfaLevel { get; set; }
[JsonProperty("application_id")]


+ 4
- 3
src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs View File

@@ -11,13 +11,14 @@ namespace Discord.Rest
public static async Task<Model> CreateThreadAsync(BaseDiscordClient client, ITextChannel channel, string name, ThreadType type = ThreadType.PublicThread,
ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null)
{
if (autoArchiveDuration == ThreadArchiveDuration.OneWeek && !channel.Guild.Features.Contains("SEVEN_DAY_THREAD_ARCHIVE"))
var features = channel.Guild.Features;
if (autoArchiveDuration == ThreadArchiveDuration.OneWeek && !features.HasFeature(GuildFeature.SevenDayThreadArchive))
throw new ArgumentException($"The guild {channel.Guild.Name} does not have the SEVEN_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration));

if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !channel.Guild.Features.Contains("THREE_DAY_THREAD_ARCHIVE"))
if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !features.HasFeature(GuildFeature.ThreeDayThreadArchive))
throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration));

if (type == ThreadType.PrivateThread && !channel.Guild.Features.Contains("PRIVATE_THREADS"))
if (type == ThreadType.PrivateThread && !features.HasFeature(GuildFeature.PrivateThreads))
throw new ArgumentException($"The guild {channel.Guild.Name} does not have the PRIVATE_THREADS feature!", nameof(type));

var args = new StartThreadParams


+ 3
- 8
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -22,7 +22,6 @@ namespace Discord.Rest
private ImmutableDictionary<ulong, RestRole> _roles;
private ImmutableArray<GuildEmote> _emotes;
private ImmutableArray<CustomSticker> _stickers;
private ImmutableArray<string> _features;

/// <inheritdoc />
public string Name { get; private set; }
@@ -90,9 +89,10 @@ namespace Discord.Rest
public NsfwLevel NsfwLevel { get; private set; }
/// <inheritdoc />
public bool IsBoostProgressBarEnabled { get; private set; }

/// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; }
/// <inheritdoc />
public GuildFeatures Features { get; private set; }

/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
@@ -118,8 +118,6 @@ namespace Discord.Rest
/// <inheritdoc />
public IReadOnlyCollection<GuildEmote> Emotes => _emotes;
public IReadOnlyCollection<CustomSticker> Stickers => _stickers;
/// <inheritdoc />
public IReadOnlyCollection<string> Features => _features;

internal RestGuild(BaseDiscordClient client, ulong id)
: base(client, id)
@@ -185,10 +183,7 @@ namespace Discord.Rest
else
_emotes = ImmutableArray.Create<GuildEmote>();

if (model.Features != null)
_features = model.Features.ToImmutableArray();
else
_features = ImmutableArray.Create<string>();
Features = model.Features;

var roles = ImmutableDictionary.CreateBuilder<ulong, RestRole>();
if (model.Roles != null)


+ 6
- 0
src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs View File

@@ -18,6 +18,12 @@ namespace Discord.Rest
{
var args = new RoleProperties();
func(args);

if (args.Icon.IsSpecified)
{
role.Guild.Features.EnsureFeature(GuildFeature.RoleIcons);
}

var apiArgs = new API.Rest.ModifyGuildRoleParams
{
Color = args.Color.IsSpecified ? args.Color.Value.RawValue : Optional.Create<uint>(),


+ 2
- 0
src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs View File

@@ -87,6 +87,8 @@ namespace Discord.Net.Converters
return MessageComponentConverter.Instance;
if (type == typeof(API.Interaction))
return InteractionConverter.Instance;
if (type == typeof(GuildFeatures))
return GuildFeaturesConverter.Instance;

//Entities
var typeInfo = type.GetTypeInfo();


+ 60
- 0
src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs View File

@@ -0,0 +1,60 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Discord.Net.Converters
{
internal class GuildFeaturesConverter : JsonConverter
{
public static GuildFeaturesConverter Instance
=> new GuildFeaturesConverter();

public override bool CanConvert(Type objectType) => true;
public override bool CanWrite => false;
public override bool CanRead => true;


private Regex _readRegex = new Regex(@"_(\w)");

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JToken.Load(reader);
var arr = obj.ToObject<string[]>();

GuildFeature features = GuildFeature.None;
List<string> experimental = new();

foreach(var item in arr)
{
var name = _readRegex.Replace(item.ToLower(), (x) =>
{
return x.Groups[1].Value.ToUpper();
});

name = name[0].ToString().ToUpper() + new string(name.Skip(1).ToArray());

try
{
var result = (GuildFeature)Enum.Parse(typeof(GuildFeature), name);

features |= result;
}
catch
{
experimental.Add(item);
}
}

return new GuildFeatures(features, experimental.ToArray());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

+ 14
- 0
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -572,6 +572,8 @@ namespace Discord.WebSocket
{
if (ConnectionState == ConnectionState.Connected)
{
EnsureGatewayIntent(GatewayIntents.GuildMembers);

//Race condition leads to guilds being requested twice, probably okay
await ProcessUserDownloadsAsync(guilds.Select(x => GetGuild(x.Id)).Where(x => x != null)).ConfigureAwait(false);
}
@@ -2717,6 +2719,18 @@ namespace Discord.WebSocket
channel.Recipient.GlobalUser.RemoveRef(this);
}

internal void EnsureGatewayIntent(GatewayIntents intents)
{
if (!_gatewayIntents.HasFlag(intents))
{
var vals = Enum.GetValues(typeof(GatewayIntents)).Cast<GatewayIntents>();

var missingValues = vals.Where(x => intents.HasFlag(x) && !_gatewayIntents.HasFlag(x));

throw new InvalidOperationException($"Missing required gateway intent{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation.");
}
}

private async Task GuildAvailableAsync(SocketGuild guild)
{
if (!guild.IsConnected)


+ 3
- 8
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -42,7 +42,6 @@ namespace Discord.WebSocket
private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers;
private ImmutableArray<GuildEmote> _emotes;

private ImmutableArray<string> _features;
private AudioClient _audioClient;
#pragma warning restore IDISP002, IDISP006

@@ -129,6 +128,8 @@ namespace Discord.WebSocket
public CultureInfo PreferredCulture { get; private set; }
/// <inheritdoc />
public bool IsBoostProgressBarEnabled { get; private set; }
/// <inheritdoc />
public GuildFeatures Features { get; private set; }

/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
@@ -333,8 +334,6 @@ namespace Discord.WebSocket
/// </summary>
public IReadOnlyCollection<SocketCustomSticker> Stickers
=> _stickers.Select(x => x.Value).ToImmutableArray();
/// <inheritdoc />
public IReadOnlyCollection<string> Features => _features;
/// <summary>
/// Gets a collection of users in this guild.
/// </summary>
@@ -370,7 +369,6 @@ namespace Discord.WebSocket
{
_audioLock = new SemaphoreSlim(1, 1);
_emotes = ImmutableArray.Create<GuildEmote>();
_features = ImmutableArray.Create<string>();
}
internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model)
{
@@ -508,10 +506,7 @@ namespace Discord.WebSocket
else
_emotes = ImmutableArray.Create<GuildEmote>();

if (model.Features != null)
_features = model.Features.ToImmutableArray();
else
_features = ImmutableArray.Create<string>();
Features = model.Features;

var roles = new ConcurrentDictionary<ulong, SocketRole>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05));
if (model.Roles != null)


Loading…
Cancel
Save