diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 9f5c4f4ab..dc57c082a 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -4,4 +4,13 @@ netstandard2.0 + + + + + + + + + diff --git a/src/Discord.Net/Entities/Guilds/DefaultMessageNotifications.cs b/src/Discord.Net/Entities/Guilds/DefaultMessageNotifications.cs new file mode 100644 index 000000000..474ec1d79 --- /dev/null +++ b/src/Discord.Net/Entities/Guilds/DefaultMessageNotifications.cs @@ -0,0 +1,19 @@ +using Model = Wumpus.Entities.DefaultMessageNotifications; + +namespace Discord +{ + /// + /// Specifies the default message notification behavior the guild uses. + /// + public enum DefaultMessageNotifications + { + /// + /// By default, all messages will trigger notifications. + /// + AllMessages = 0, + /// + /// By default, only mentions will trigger notifications. + /// + MentionsOnly = 1 + } +} diff --git a/src/Discord.Net/Entities/Guilds/ExplicitContentFilterLevel.cs b/src/Discord.Net/Entities/Guilds/ExplicitContentFilterLevel.cs new file mode 100644 index 000000000..54c0bdafe --- /dev/null +++ b/src/Discord.Net/Entities/Guilds/ExplicitContentFilterLevel.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + public enum ExplicitContentFilterLevel + { + /// No messages will be scanned. + Disabled = 0, + /// Scans messages from all guild members that do not have a role. + /// Recommented option for servers that use roles for trusted membership. + MembersWithoutRoles = 1, + /// Scan messages sent by all guild members. + AllMembers = 2 + } +} diff --git a/src/Discord.Net/Entities/Guilds/IGuild.cs b/src/Discord.Net/Entities/Guilds/IGuild.cs new file mode 100644 index 000000000..56f67c9ce --- /dev/null +++ b/src/Discord.Net/Entities/Guilds/IGuild.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Discord +{ + public interface IGuild : IDeletable, ISnowflakeEntity + { + /// + /// Gets the name of this guild. + /// + /// + /// A string containing the name of this guild. + /// + string Name { get; } + /// + /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are + /// automatically moved to the AFK voice channel. + /// + /// + /// An representing the amount of time in seconds for a user to be marked as inactive + /// and moved into the AFK voice channel. + /// + int AFKTimeout { get; } + /// + /// Gets a value that indicates whether this guild is embeddable (i.e. can use widget). + /// + /// + /// true if this guild can be embedded via widgets; otherwise false. + /// + bool IsEmbeddable { get; } + /// + /// Gets the default message notifications for users who haven't explicitly set their notification settings. + /// + DefaultMessageNotifications DefaultMessageNotifications { get; } + /// + /// Gets the level of Multi-Factor Authentication requirements a user must fulfill before being allowed to + /// perform administrative actions in this guild. + /// + /// + /// The level of MFA requirement. + /// + MfaLevel MfaLevel { get; } + /// + /// Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. + /// + /// + /// The level of requirements. + /// + VerificationLevel VerificationLevel { get; } + /// + /// Gets the level of content filtering applied to user's content in a Guild. + /// + /// + /// The level of explicit content filtering. + /// + ExplicitContentFilterLevel ExplicitContentFilter { get; } + /// + /// Gets the ID of this guild's icon. + /// + /// + /// An identifier for the splash image; null if none is set. + /// + string IconId { get; } + /// + /// Gets the URL of this guild's icon. + /// + /// + /// A URL pointing to the guild's icon; null if none is set. + /// + string IconUrl { get; } + /// + /// Gets the ID of this guild's splash image. + /// + /// + /// An identifier for the splash image; null if none is set. + /// + string SplashId { get; } + /// + /// Gets the URL of this guild's splash image. + /// + /// + /// A URL pointing to the guild's splash image; null if none is set. + /// + string SplashUrl { get; } + /// + /// Determines if this guild is currently connected and ready to be used. + /// + /// + /// + /// This property only applies to a guild fetched via the gateway; it will always return false on guilds fetched via REST. + /// + /// This boolean is used to determine if the guild is currently connected to the WebSocket and is ready to be used/accessed. + /// + /// + /// true if this guild is currently connected and ready to be used; otherwise false. + /// + bool Available { get; } + + /// + /// Gets the ID of the AFK voice channel for this guild. + /// + /// + /// A representing the snowflake identifier of the AFK voice channel; null if + /// none is set. + /// + ulong? AFKChannelId { get; } + /// + /// Gets the ID of the widget embed channel of this guild. + /// + /// + /// A representing the snowflake identifier of the embedded channel found within the + /// widget settings of this guild; null if none is set. + /// + ulong? EmbedChannelId { get; } + /// + /// Gets the ID of the channel where randomized welcome messages are sent. + /// + /// + /// A representing the snowflake identifier of the system channel where randomized + /// welcome messages are sent; null if none is set. + /// + ulong? SystemChannelId { get; } + /// + /// Gets the ID of the user that owns this guild. + /// + /// + /// A representing the snowflake identifier of the user that owns this guild. + /// + ulong OwnerId { get; } + /// + /// Gets the application ID of the guild creator if it is bot-created. + /// + /// + /// A representing the snowflake identifier of the application ID that created this guild, or null if it was not bot-created. + /// + ulong? ApplicationId { get; } + /// + /// Gets the ID of the region hosting this guild's voice channels. + /// + /// + /// A string containing the identifier for the voice region that this guild uses (e.g. eu-central). + /// + string VoiceRegionId { get; } + } +} diff --git a/src/Discord.Net/Entities/Guilds/MfaLevel.cs b/src/Discord.Net/Entities/Guilds/MfaLevel.cs new file mode 100644 index 000000000..57edac2b0 --- /dev/null +++ b/src/Discord.Net/Entities/Guilds/MfaLevel.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + /// + /// Specifies the guild's Multi-Factor Authentication (MFA) level requirement. + /// + public enum MfaLevel + { + /// + /// Users have no additional MFA restriction on this guild. + /// + Disabled = 0, + /// + /// Users must have MFA enabled on their account to perform administrative actions. + /// + Enabled = 1 + } +} diff --git a/src/Discord.Net/Entities/Guilds/VerificationLevel.cs b/src/Discord.Net/Entities/Guilds/VerificationLevel.cs new file mode 100644 index 000000000..3a5ae0468 --- /dev/null +++ b/src/Discord.Net/Entities/Guilds/VerificationLevel.cs @@ -0,0 +1,29 @@ +namespace Discord +{ + /// + /// Specifies the verification level the guild uses. + /// + public enum VerificationLevel + { + /// + /// Users have no additional restrictions on sending messages to this guild. + /// + None = 0, + /// + /// Users must have a verified email on their account. + /// + Low = 1, + /// + /// Users must fulfill the requirements of Low and be registered on Discord for at least 5 minutes. + /// + Medium = 2, + /// + /// Users must fulfill the requirements of Medium and be a member of this guild for at least 10 minutes. + /// + High = 3, + /// + /// Users must fulfill the requirements of High and must have a verified phone on their Discord account. + /// + Extreme = 4 + } +} diff --git a/src/Discord.Net/Entities/IDeletable.cs b/src/Discord.Net/Entities/IDeletable.cs new file mode 100644 index 000000000..faf1ec685 --- /dev/null +++ b/src/Discord.Net/Entities/IDeletable.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Determines whether the object is deletable or not. + /// + public interface IDeletable + { + /// + /// Deletes this object and all its children. + /// + /// The options to be used when sending the request. + Task DeleteAsync(/*RequestOptions options = null*/); + } +} diff --git a/src/Discord.Net/Entities/IEntity.cs b/src/Discord.Net/Entities/IEntity.cs new file mode 100644 index 000000000..e2617244e --- /dev/null +++ b/src/Discord.Net/Entities/IEntity.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord +{ + public interface IEntity + where TId : IEquatable + { + /// + /// Gets the that created this object. + /// + IDiscordClient Discord { get; } + + /// + /// Gets the unique identifier for this object. + /// + TId Id { get; } + } +} diff --git a/src/Discord.Net/Entities/ISnowflakeEntity.cs b/src/Discord.Net/Entities/ISnowflakeEntity.cs new file mode 100644 index 000000000..6f2c7512b --- /dev/null +++ b/src/Discord.Net/Entities/ISnowflakeEntity.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord +{ + /// Represents a Discord snowflake entity. + public interface ISnowflakeEntity : IEntity + { + /// + /// Gets when the snowflake was created. + /// + /// + /// A representing when the entity was first created. + /// + DateTimeOffset CreatedAt { get; } + } +} diff --git a/src/Discord.Net/Models/Guilds/Guild.cs b/src/Discord.Net/Models/Guilds/Guild.cs new file mode 100644 index 000000000..68c217b9f --- /dev/null +++ b/src/Discord.Net/Models/Guilds/Guild.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Wumpus.Entities; +using Model = Wumpus.Entities.Guild; + +namespace Discord +{ + internal class Guild : SnowflakeEntity, IGuild + { + public Guild(Model model, IDiscordClient client) : base(client) + { + Name = model.Name.ToString(); + AFKTimeout = model.AfkTimeout; + IsEmbeddable = model.EmbedEnabled.GetValueOrDefault(false); + + // FYI: when casting these, make sure Wumpus is using the same schema as us, otherwise these values will not match up. + DefaultMessageNotifications = (DefaultMessageNotifications)model.DefaultMessageNotifications; + MfaLevel = (MfaLevel)model.MfaLevel; + VerificationLevel = (VerificationLevel)model.VerificationLevel; + ExplicitContentFilter = (ExplicitContentFilterLevel)model.ExplicitContentFilter; + + IconId = model.Icon?.Hash.ToString(); + IconUrl = null; // TODO: port CDN + SplashId = model.Splash?.Hash.ToString(); + SplashUrl = null; // TODO: port CDN + + Available = model is GatewayGuild; + + AFKChannelId = model.AfkChannelId; + EmbedChannelId = model.EmbedChannelId.IsSpecified ? model.EmbedChannelId.Value : null; + SystemChannelId = model.SystemChannelId; + + OwnerId = model.OwnerId; + ApplicationId = model.ApplicationId; + VoiceRegionId = null; // TODO? + } + + public string Name { get; set; } + public int AFKTimeout { get; set; } + public bool IsEmbeddable { get; set; } + + public DefaultMessageNotifications DefaultMessageNotifications { get; set; } + public MfaLevel MfaLevel { get; set; } + public VerificationLevel VerificationLevel { get; set; } + public ExplicitContentFilterLevel ExplicitContentFilter { get; set; } + + public string IconId { get; set; } + public string IconUrl { get; set; } + public string SplashId { get; set; } + public string SplashUrl { get; set; } + + public bool Available { get; set; } + + public ulong? AFKChannelId { get; set; } + public ulong? EmbedChannelId { get; set; } + public ulong? SystemChannelId { get; set; } + + public ulong OwnerId { get; set; } + public ulong? ApplicationId { get; set; } + public string VoiceRegionId { get; set; } + + public Task DeleteAsync() => throw new NotImplementedException(); + } +} diff --git a/src/Discord.Net/Models/SnowflakeEntity.cs b/src/Discord.Net/Models/SnowflakeEntity.cs new file mode 100644 index 000000000..3d9cf7093 --- /dev/null +++ b/src/Discord.Net/Models/SnowflakeEntity.cs @@ -0,0 +1,28 @@ +using System; + +namespace Discord +{ + internal abstract class SnowflakeEntity : ISnowflakeEntity + { + private DateTimeOffset? _createdAt; + + public SnowflakeEntity(IDiscordClient discord) + { + Discord = discord; + } + + public IDiscordClient Discord { get; set; } + public ulong Id { get; set; } + + public DateTimeOffset CreatedAt + { + get + { + if (_createdAt.HasValue) + return _createdAt.Value; + _createdAt = SnowflakeUtilities.FromSnowflake(Id); + return _createdAt.Value; + } + } + } +} diff --git a/src/Discord.Net/Utilities/SnowflakeUtilities.cs b/src/Discord.Net/Utilities/SnowflakeUtilities.cs new file mode 100644 index 000000000..d9f687d4d --- /dev/null +++ b/src/Discord.Net/Utilities/SnowflakeUtilities.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Discord +{ + /// + /// Provides a series of helper methods for handling snowflake identifiers. + /// + public static class SnowflakeUtilities + { + /// + /// Resolves the time of which the snowflake is generated. + /// + /// The snowflake identifier to resolve. + /// + /// A representing the time for when the object is geenrated. + /// + public static DateTimeOffset FromSnowflake(ulong value) + => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); + /// + /// Generates a pseudo-snowflake identifier with a . + /// + /// The time to be used in the new snowflake. + /// + /// A representing the newly generated snowflake identifier. + /// + public static ulong ToSnowflake(DateTimeOffset value) + => ((ulong)value.ToUnixTimeMilliseconds() - 1420070400000UL) << 22; + } +} diff --git a/test/Discord.Tests.Unit/Discord.Tests.Unit.csproj b/test/Discord.Tests.Unit/Discord.Tests.Unit.csproj index 4ec64e7cb..269fed778 100644 --- a/test/Discord.Tests.Unit/Discord.Tests.Unit.csproj +++ b/test/Discord.Tests.Unit/Discord.Tests.Unit.csproj @@ -12,4 +12,8 @@ + + + + diff --git a/test/Discord.Tests.Unit/UnitTest1.cs b/test/Discord.Tests.Unit/UnitTest1.cs deleted file mode 100644 index a5be20894..000000000 --- a/test/Discord.Tests.Unit/UnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Xunit; - -namespace Discord.Tests.Unit -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} diff --git a/test/Discord.Tests.Unit/Utilities/SnowflakeTests.cs b/test/Discord.Tests.Unit/Utilities/SnowflakeTests.cs new file mode 100644 index 000000000..bc5b98dbe --- /dev/null +++ b/test/Discord.Tests.Unit/Utilities/SnowflakeTests.cs @@ -0,0 +1,22 @@ +using System; +using Xunit; + +namespace Discord.Tests.Unit +{ + public class SnowflakeTests + { + [Fact] + public void FromSnowflake() + { + Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1420070400000), SnowflakeUtilities.FromSnowflake(0)); + Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1439474045698), SnowflakeUtilities.FromSnowflake(81384788765712384)); + } + + [Fact] + public void ToSnowflake() + { + Assert.Equal(0UL, SnowflakeUtilities.ToSnowflake(DateTimeOffset.FromUnixTimeMilliseconds(1420070400000))); + Assert.Equal(81384788765704192UL, SnowflakeUtilities.ToSnowflake(DateTimeOffset.FromUnixTimeMilliseconds(1439474045698))); + } + } +}