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)));
+ }
+ }
+}