diff --git a/docs/features/management.rst b/docs/features/management.rst deleted file mode 100644 index accbf5d94..000000000 --- a/docs/features/management.rst +++ /dev/null @@ -1,4 +0,0 @@ -|stub| Server Management -======================== - -|stub-desc| \ No newline at end of file diff --git a/docs/features/server-management.rst b/docs/features/server-management.rst new file mode 100644 index 000000000..d555875a8 --- /dev/null +++ b/docs/features/server-management.rst @@ -0,0 +1,53 @@ +Server Management +================= + +Discord.Net will allow you to manage most settings of a Discord server. + +Usage +----- + +You can create Channels, Invites, and Roles on a server using the CreateChannel, CreateInvite, and CreateRole function of a Server, respectively. + +You may also edit a server's name, icon, and region. + +.. code-block:: c# + + // Create a Channel and retrieve the Channel object + var _channel = await _server.CreateChannel("announcements", ChannelType.Text); + + // Create an Invite and retrieve the Invite object + var _invite = await _server.CreateInvite(maxAge: null, maxUses: 25, tempMembership: false, withXkcd: false); + + // Create a Role and retrieve the Role object + var _role = await _server.CreateRole(name: "Bots", permissions: null, color: Color.DarkMagenta, isHoisted: false); + + // Edit a server + var _ioStream = new System.IO.StreamReader("clock-0500-1952.png").BaseStream + _server.Edit(name: "19:52 | UTC-05:00", region: "east", icon: _ioStream, iconType: ImageType.Png); + + // Prune Users + var _pruneCount = await _server.PruneUsers(30, true); + +Invite Parameters +----------------- + +maxAge: The time (in seconds) until the invite expires. Use null for infinite. +maxUses: The maximum amount of uses the invite has before it expires. +tempMembership: Whether or not to kick a user when they disconnect. +withXkcd: Generate the invite with an XKCD 936 style URL + +Role Parameters +--------------- + +name: The name of the role +permissions: A set of ServerPermissions for the role to use by default +color: The color of the role, recommended to use Discord.Color +isHoisted: Whether a role's users should be displayed separately from other users in the user list. + +Edit Parameters +--------------- + +name: The server's name +region: The region the voice server is hosted in +icon: A System.IO.Stream that will read an image file +iconType: The type of image being sent (png/jpeg). diff --git a/docs/features/user-management.rst b/docs/features/user-management.rst new file mode 100644 index 000000000..972b3ab4b --- /dev/null +++ b/docs/features/user-management.rst @@ -0,0 +1,22 @@ +User Management +=============== + +Banning +------- + +To ban a user, invoke the Ban function on a Server object. + +.. code-block:: c# + + _server.Ban(_user, 30); + +The pruneDays parameter, which defaults to 0, will remove all messages from a user dating back to the specified amount of days. + +Kicking +------- + +To kick a user, invoke the Kick function on the User. + +.. code-block:: c# + + _user.Kick(); diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 1e8ee492e..f9dfd857d 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -18,14 +18,16 @@ You can get Discord.Net from NuGet: * `Discord.Net`_ * `Discord.Net.Commands`_ * `Discord.Net.Modules`_ +* `Discord.Net.Audio`_ If you have trouble installing from NuGet, try installing dependencies manually. You can also pull the latest source from `GitHub`_ -.. _Discord.Net: https://www.nuget.org/packages/Discord.Net/0.8.1-beta2 -.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands/0.8.1-beta2 -.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules/0.8.1-beta2 +.. _Discord.Net: https://www.nuget.org/packages/Discord.Net +.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands +.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules +.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Audio .. _GitHub: https://github.com/RogueException/Discord.Net/ Async diff --git a/docs/index.rst b/docs/index.rst index c4469cd46..d2ff662af 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,8 @@ This Documentation is **currently undergoing a rewrite**. Some pages (marked wit getting_started features/logging - features/management + features/server-management + features/user-management features/permissions features/commands features/voice diff --git a/ref/Discord.Net.xproj b/ref/Discord.Net.xproj new file mode 100644 index 000000000..d3559797d --- /dev/null +++ b/ref/Discord.Net.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 5b2afee6-fff6-4ba2-be12-61b283b72ac0 + Discord + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/ref/DiscordClient.cs b/ref/DiscordClient.cs new file mode 100644 index 000000000..aa777e04f --- /dev/null +++ b/ref/DiscordClient.cs @@ -0,0 +1,75 @@ +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord +{ + /// Provides a connection to the DiscordApp service. + public class DiscordClient : IDisposable + { + public event EventHandler Log = delegate { }; + + public event EventHandler LoggedIn = delegate { }; + public event EventHandler LoggedOut = delegate { }; + public event EventHandler Connected = delegate { }; + public event EventHandler Disconnected = delegate { }; + public event EventHandler VoiceConnected = delegate { }; + public event EventHandler VoiceDisconnected = delegate { }; + + public event EventHandler ChannelCreated = delegate { }; + public event EventHandler ChannelUpdated = delegate { }; + public event EventHandler ChannelDestroyed = delegate { }; + public event EventHandler MessageAcknowledged = delegate { }; + public event EventHandler MessageDeleted = delegate { }; + public event EventHandler MessageReceived = delegate { }; + public event EventHandler MessageSent = delegate { }; + public event EventHandler MessageUpdated = delegate { }; + public event EventHandler ProfileUpdated = delegate { }; + public event EventHandler RoleCreated = delegate { }; + public event EventHandler RoleUpdated = delegate { }; + public event EventHandler RoleDeleted = delegate { }; + public event EventHandler JoinedServer = delegate { }; + public event EventHandler LeftServer = delegate { }; + public event EventHandler ServerAvailable = delegate { }; + public event EventHandler ServerUpdated = delegate { }; + public event EventHandler ServerUnavailable = delegate { }; + public event EventHandler UserBanned = delegate { }; + public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserJoined = delegate { }; + public event EventHandler UserLeft = delegate { }; + public event EventHandler UserUpdated = delegate { }; + public event EventHandler UserUnbanned = delegate { }; + + public MessageQueue MessageQueue { get; } + public IRestClient RestClient { get; } + public GatewaySocket GatewaySocket { get; } + public Profile CurrentUser { get; } + + public DiscordClient() { } + public DiscordClient(DiscordConfig config) { } + + public Task Login(string token) => null; + public Task Logout() => null; + + public Task Connect() => null; + public Task Connect(int connectionId, int totalConnections) => null; + public Task Disconnect() => null; + + public Task> GetPrivateChannels() => null; + public Task GetPrivateChannel(ulong userId) => null; + public Task GetInvite(string inviteIdOrXkcd) => null; + public Task> GetRegions() => null; + public Task GetRegion(string id) => null; + public Task> GetServers() => null; + public Task GetServer(ulong id) => null; + + public Task CreatePrivateChannel(ulong userId) => null; + public Task CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) => null; + + public void Dispose() { } + } +} \ No newline at end of file diff --git a/ref/DiscordConfig.cs b/ref/DiscordConfig.cs new file mode 100644 index 000000000..e6b1a5568 --- /dev/null +++ b/ref/DiscordConfig.cs @@ -0,0 +1,65 @@ +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using System.Reflection; + +namespace Discord +{ + public class DiscordConfig + { + public const int MaxMessageSize = 2000; + public const int MaxMessagesPerBatch = 100; + + public const string LibName = "Discord.Net"; + public static string LibVersion => typeof(DiscordConfig).GetTypeInfo().Assembly?.GetName().Version.ToString(3) ?? "Unknown"; + public const string LibUrl = "https://github.com/RogueException/Discord.Net"; + + public const string ClientAPIUrl = "https://discordapp.com/api/"; + public const string CDNUrl = "https://cdn.discordapp.com/"; + public const string InviteUrl = "https://discord.gg/"; + + /// Gets or sets name of your application, used in the user agent. + public string AppName { get; set; } = null; + /// Gets or sets url to your application, used in the user agent. + public string AppUrl { get; set; } = null; + /// Gets or sets the version of your application, used in the user agent. + public string AppVersion { get; set; } = null; + + /// Gets or sets the minimum log level severity that will be sent to the LogMessage event. + public LogSeverity LogLevel { get; set; } = LogSeverity.Info; + + /// Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. + public int ConnectionTimeout { get; set; } = 30000; + /// Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. + public int ReconnectDelay { get; set; } = 1000; + /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. + public int FailedReconnectDelay { get; set; } = 15000; + + //Performance + + /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. + public int MessageCacheSize { get; set; } = 100; + /// + /// Gets or sets whether the permissions cache should be used. + /// This makes operations such as User.GetPermissions(Channel), User.ServerPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage. + /// + public bool UsePermissionsCache { get; set; } = true; + /// Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. + public bool EnablePreUpdateEvents { get; set; } = true; + /// + /// Gets or sets the max number of users a server may have for offline users to be included in the READY packet. Max is 250. + /// Decreasing this may reduce CPU usage while increasing login time and network usage. + /// + public int LargeThreshold { get; set; } = 250; + + //Engines + + /// Gets or sets the REST engine to use.. Defaults to DefaultRestClientProvider, which uses .Net's HttpClient class. + public IRestClientProvider RestClientProvider { get; set; } = null; + /// + /// Gets or sets the WebSocket engine to use. Defaults to DefaultWebSocketProvider, which uses .Net's WebSocketClient class. + /// WebSockets are only used if DiscordClient.Connect() is called. + /// + public IWebSocketProvider WebSocketProvider { get; set; } = null; + } +} + diff --git a/ref/Entities/Channels/IChannel.cs b/ref/Entities/Channels/IChannel.cs new file mode 100644 index 000000000..fb82fb30d --- /dev/null +++ b/ref/Entities/Channels/IChannel.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public interface IChannel : IEntity + { + /// Gets the type flags for this channel. + ChannelType Type { get; } + /// Gets the name of this channel. + string Name { get; } + + /// Gets a user in this channel with the given id. + Task GetUser(ulong id); + /// Gets a collection of all users in this channel. + Task> GetUsers(); + } +} diff --git a/ref/Entities/Channels/IPublicChannel.cs b/ref/Entities/Channels/IPublicChannel.cs new file mode 100644 index 000000000..bd005a288 --- /dev/null +++ b/ref/Entities/Channels/IPublicChannel.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public interface IPublicChannel : IChannel + { + /// Gets the server this channel is a member of. + Server Server { get; } + /// Gets a collection of permission overwrites for this channel. + IEnumerable PermissionOverwrites { get; } + /// Gets the position of this public channel relative to others of the same type. + int Position { get; } + + /// Gets a user in this channel with the given id. + new Task GetUser(ulong id); + /// Gets a collection of all users in this channel. + new Task> GetUsers(); + + /// Gets the permission overwrite for a specific user, or null if one does not exist. + OverwritePermissions? GetPermissionOverwrite(ServerUser user); + /// Gets the permission overwrite for a specific role, or null if one does not exist. + OverwritePermissions? GetPermissionOverwrite(Role role); + /// Downloads a collection of all invites to this server. + Task> GetInvites(); + + /// Adds or updates the permission overwrite for the given user. + Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions); + /// Adds or updates the permission overwrite for the given role. + Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions); + /// Removes the permission overwrite for the given user, if one exists. + Task RemovePermissionOverwrite(ServerUser user); + /// Removes the permission overwrite for the given role, if one exists. + Task RemovePermissionOverwrite(Role role); + + /// Creates a new invite to this channel. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false); + } +} diff --git a/ref/Entities/Channels/ITextChannel.cs b/ref/Entities/Channels/ITextChannel.cs new file mode 100644 index 000000000..f3701abbf --- /dev/null +++ b/ref/Entities/Channels/ITextChannel.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public interface ITextChannel : IChannel + { + /// Gets the message in this text channel with the given id, or null if none was found. + Task GetMessage(ulong id); + /// Gets the last N messages from this text channel. + /// The maximum number of messages to retrieve. + Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. + /// The maximum number of messages to retrieve. + /// The message to start downloading relative to. + /// The direction, from relativeMessageId, to download messages in. + Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before); + + /// Sends a message to this text channel. + Task SendMessage(string text, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + Task SendFile(string filePath, string text = null, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false); + + /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. + Task SendIsTyping(); + } +} diff --git a/ref/Entities/Channels/PrivateChannel.cs b/ref/Entities/Channels/PrivateChannel.cs new file mode 100644 index 000000000..ee72c0828 --- /dev/null +++ b/ref/Entities/Channels/PrivateChannel.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public class PrivateChannel : ITextChannel, IChannel + { + /// + public DiscordClient Discord { get; } + /// + public EntityState State { get; } + /// + public ulong Id { get; } + /// + public PrivateUser Recipient { get; } + /// + public PrivateUser CurrentUser { get; } + + /// + ChannelType IChannel.Type => ChannelType.Private | ChannelType.Text; + /// + public string Name { get; } + + /// + public Task GetUser(ulong id) => null; + /// + Task IChannel.GetUser(ulong id) => null; + /// + public Task> GetUsers() => null; + /// + Task> IChannel.GetUsers() => null; + /// + public Task GetMessage(ulong id) => null; + /// + public Task> GetMessages(int limit = 100) => null; + /// + public Task> GetMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) => null; + + /// + public Task SendMessage(string text, bool isTTS = false) => null; + /// + public Task SendFile(string filePath, string text = null, bool isTTS = false) => null; + /// + public Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) => null; + + /// + public Task SendIsTyping() => null; + + /// + public Task Update() => null; + /// + public Task Delete() => null; + } +} diff --git a/ref/Entities/Channels/TextChannel.cs b/ref/Entities/Channels/TextChannel.cs new file mode 100644 index 000000000..0b1b81c77 --- /dev/null +++ b/ref/Entities/Channels/TextChannel.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public class TextChannel : ITextChannel, IMentionable, IModifiable + { + public sealed class Properties + { + public string Name { get; } + public string Topic { get; } + public int Position { get; } + } + + /// + public EntityState State { get; } + /// + public ulong Id { get; } + /// + public Server Server { get; } + + /// + public DiscordClient Discord { get; } + /// + public ChannelType Type => ChannelType.Public | ChannelType.Text; + + /// + public string Name { get; } + /// + public string Topic { get; } + /// + public int Position { get; } + + /// + public string Mention { get; } + /// + public IEnumerable PermissionOverwrites { get; } + + /// + public OverwritePermissions? GetPermissionOverwrite(ServerUser user) => null; + /// + public OverwritePermissions? GetPermissionOverwrite(Role role) => null; + /// + public Task GetUser(ulong id) => null; + /// + Task IChannel.GetUser(ulong id) => null; + /// + public Task> GetUsers() => null; + /// + Task> IChannel.GetUsers() => null; + /// + public Task GetMessage(ulong id) => null; + /// + public Task> GetMessages(int limit = 100) => null; + /// + public Task> GetMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) => null; + /// + public Task> GetInvites() => null; + + /// + public Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions) => null; + /// + public Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions) => null; + /// + public Task RemovePermissionOverwrite(ServerUser user) => null; + /// + public Task RemovePermissionOverwrite(Role role) => null; + + /// + public Task SendMessage(string text, bool isTTS = false) => null; + /// + public Task SendFile(string filePath, string text = null, bool isTTS = false) => null; + /// + public Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) => null; + + /// + public Task SendIsTyping() => null; + + /// + public Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null; + + /// + public Task Update() => null; + /// + public Task Modify(Action func) => null; + /// + public Task Delete() => null; + } +} diff --git a/ref/Entities/Channels/VoiceChannel.cs b/ref/Entities/Channels/VoiceChannel.cs new file mode 100644 index 000000000..6552fadd7 --- /dev/null +++ b/ref/Entities/Channels/VoiceChannel.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public class VoiceChannel : IPublicChannel, IModifiable + { + public sealed class Properties + { + public string Name { get; } + public int Bitrate { get; set; } + public int Position { get; } + } + + /// + public ulong Id { get; } + /// + public EntityState State { get; } + /// + public Server Server { get; } + + /// + public DiscordClient Discord { get; } + /// + ChannelType IChannel.Type => ChannelType.Public | ChannelType.Voice; + + /// + public string Name { get; } + /// + public int Position { get; } + /// + public int Bitrate { get; } + + /// + public string Mention { get; } + /// + public IEnumerable PermissionOverwrites { get; } + + /// + public OverwritePermissions? GetPermissionOverwrite(ServerUser user) => null; + /// + public OverwritePermissions? GetPermissionOverwrite(Role role) => null; + /// + public Task GetUser(ulong id) => null; + /// + Task IChannel.GetUser(ulong id) => null; + /// + public Task> GetUsers() => null; + /// + Task> IChannel.GetUsers() => null; + /// + public Task> GetInvites() => null; + + /// + public Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions) => null; + /// + public Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions) => null; + /// + public Task RemovePermissionOverwrite(ServerUser user) => null; + /// + public Task RemovePermissionOverwrite(Role role) => null; + + /// + public Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null; + + /// + public Task Update() => null; + /// + public Task Modify(Action func) => null; + /// + public Task Delete() => null; + } +} diff --git a/ref/Entities/Color.cs b/ref/Entities/Color.cs new file mode 100644 index 000000000..b3c78debf --- /dev/null +++ b/ref/Entities/Color.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + public class Color + { + public static readonly Color Default = new Color(0); + + public uint RawValue { get; } + + public Color(uint rawValue) { } + public Color(byte r, byte g, byte b) { } + public Color(float r, float g, float b) { } + + public byte R { get; } + public byte G { get; } + public byte B { get; } + } +} diff --git a/ref/Entities/IEntity.cs b/ref/Entities/IEntity.cs new file mode 100644 index 000000000..ac707a69e --- /dev/null +++ b/ref/Entities/IEntity.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public interface IEntity : IEntity + { + /// Gets the unique identifier for this object. + TId Id { get; } + } + public interface IEntity + { + /// Gets the DiscordClient that manages this object. + DiscordClient Discord { get; } + /// Gets the state of this object. + EntityState State { get; } + + /// Downloads the latest values and updates this object. + Task Update(); + } +} diff --git a/ref/Entities/IMentionable.cs b/ref/Entities/IMentionable.cs new file mode 100644 index 000000000..0a4bf439c --- /dev/null +++ b/ref/Entities/IMentionable.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + public interface IMentionable + { + string Mention { get; } + } +} diff --git a/ref/Entities/IModifiable.cs b/ref/Entities/IModifiable.cs new file mode 100644 index 000000000..f264c96f2 --- /dev/null +++ b/ref/Entities/IModifiable.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + public interface IModifiable + { + /// Modifies one or more of the properties of this object. + Task Modify(Action func); + } +} diff --git a/ref/Entities/Invite/BasicInvite.cs b/ref/Entities/Invite/BasicInvite.cs new file mode 100644 index 000000000..37cd1704d --- /dev/null +++ b/ref/Entities/Invite/BasicInvite.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public class BasicInvite : IEntity + { + public class TargetInfo + { + public ulong Id { get; } + public string Name { get; } + } + public class InviterInfo + { + public ulong Id { get; } + public string Name { get; } + public ushort Discriminator { get; } + public string AvatarId { get; } + public string AvatarUrl { get; } + } + + string IEntity.Id => Code; + public DiscordClient Discord { get; } + public EntityState State { get; } + + public string Code { get; } + public string XkcdCode { get; } + + public TargetInfo Server { get; } + public TargetInfo Channel { get; } + + public string Url { get; } + + public Task Accept() => null; + + public virtual Task Update() => null; + } +} diff --git a/ref/Entities/Invite/Invite.cs b/ref/Entities/Invite/Invite.cs new file mode 100644 index 000000000..11fead2af --- /dev/null +++ b/ref/Entities/Invite/Invite.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + public class Invite : BasicInvite + { + public int? MaxAge { get; } + public int Uses { get; } + public int? MaxUses { get; } + public bool IsRevoked { get; } + public bool IsTemporary { get; } + public DateTime CreatedAt { get; } + + public override Task Update() => null; + public Task Delete() => null; + } +} diff --git a/ref/Entities/Message.cs b/ref/Entities/Message.cs new file mode 100644 index 000000000..78c4e41bd --- /dev/null +++ b/ref/Entities/Message.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public class Message : IEntity + { + public class Attachment : File + { + public string Id { get; } + public int Size { get; } + public string Filename { get; } + } + + public class Embed + { + public string Url { get; } + public string Type { get; } + public string Title { get; } + public string Description { get; } + public EmbedLink Author { get; } + public EmbedLink Provider { get; } + public File Thumbnail { get; } + public File Video { get; } + } + + public class EmbedLink + { + public string Url { get; } + public string Name { get; } + } + + public class File + { + public string Url { get; } + public string ProxyUrl { get; } + public int? Width { get; } + public int? Height { get; } + } + + public ulong Id { get; } + public DiscordClient Discord { get; } + public EntityState State { get; } + + public ITextChannel Channel { get; } + public IUser User { get; } + public bool IsTTS { get; } + public string RawText { get; } + public string Text { get; } + public DateTime Timestamp { get; } + public DateTime? EditedTimestamp { get; } + public Attachment[] Attachments { get; } + public Embed[] Embeds { get; } + + public IReadOnlyList MentionedUsers { get; } + public IReadOnlyList MentionedChannels { get; } + public IReadOnlyList MentionedRoles { get; } + + public Server Server => null; + public bool IsAuthor => false; + + public bool IsMentioningMe(bool includeRoles = false) => false; + + public Task Update() => null; + public Task Delete() => null; + } +} diff --git a/ref/Entities/Permissions/ChannelPermissions.cs b/ref/Entities/Permissions/ChannelPermissions.cs new file mode 100644 index 000000000..d01f0430e --- /dev/null +++ b/ref/Entities/Permissions/ChannelPermissions.cs @@ -0,0 +1,53 @@ +namespace Discord +{ + public struct ChannelPermissions + { + public static ChannelPermissions None { get; } + public static ChannelPermissions TextOnly { get; } + public static ChannelPermissions PrivateOnly { get; } + public static ChannelPermissions VoiceOnly { get; } + public static ChannelPermissions All(ChannelType channelType) => default(ChannelPermissions); + + public uint RawValue { get; } + + public bool CreateInstantInvite { get; } + public bool ManagePermission { get; } + public bool ManageChannel { get; } + + public bool ReadMessages { get; } + public bool SendMessages { get; } + public bool SendTTSMessages { get; } + public bool ManageMessages { get; } + public bool EmbedLinks { get; } + public bool AttachFiles { get; } + public bool ReadMessageHistory { get; } + public bool MentionEveryone { get; } + + public bool Connect { get; } + public bool Speak { get; } + public bool MuteMembers { get; } + public bool DeafenMembers { get; } + public bool MoveMembers { get; } + public bool UseVoiceActivation { get; } + + public ChannelPermissions(bool? createInstantInvite = null, bool? managePermissions = null, + bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, + bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, + bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null) + : this() + { + } + public ChannelPermissions(uint rawValue) + : this() + { + } + + public ChannelPermissions Modify(bool? createInstantInvite = null, bool? managePermissions = null, + bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, + bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, + bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null) + => default(ChannelPermissions); + } +} diff --git a/ref/Entities/Permissions/OverwritePermissions.cs b/ref/Entities/Permissions/OverwritePermissions.cs new file mode 100644 index 000000000..1cda173ec --- /dev/null +++ b/ref/Entities/Permissions/OverwritePermissions.cs @@ -0,0 +1,50 @@ +namespace Discord +{ + public struct OverwritePermissions + { + public static OverwritePermissions InheritAll { get; } + + public uint AllowValue { get; } + public uint DenyValue { get; } + + public PermValue CreateInstantInvite { get; } + public PermValue ManagePermissions { get; } + public PermValue ManageChannel { get; } + public PermValue ReadMessages { get; } + public PermValue SendMessages { get; } + public PermValue SendTTSMessages { get; } + public PermValue ManageMessages { get; } + public PermValue EmbedLinks { get; } + public PermValue AttachFiles { get; } + public PermValue ReadMessageHistory { get; } + public PermValue MentionEveryone { get; } + + public PermValue Connect { get; } + public PermValue Speak { get; } + public PermValue MuteMembers { get; } + public PermValue DeafenMembers { get; } + public PermValue MoveMembers { get; } + public PermValue UseVoiceActivation { get; } + + public OverwritePermissions(PermValue? createInstantInvite = null, PermValue? managePermissions = null, + PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, + PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, + PermValue? moveMembers = null, PermValue? useVoiceActivation = null) + : this() + { + } + + public OverwritePermissions(uint allow = 0, uint deny = 0) + : this() + { + } + + public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? managePermissions = null, + PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, + PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, + PermValue? moveMembers = null, PermValue? useVoiceActivation = null) + => default(OverwritePermissions); + } +} diff --git a/ref/Entities/Permissions/PermissionOverwriteEntry.cs b/ref/Entities/Permissions/PermissionOverwriteEntry.cs new file mode 100644 index 000000000..bbc11fba8 --- /dev/null +++ b/ref/Entities/Permissions/PermissionOverwriteEntry.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public struct PermissionOverwriteEntry + { + public PermissionTarget TargetType { get; } + public ulong TargetId { get; } + public OverwritePermissions Permissions { get; } + } +} diff --git a/ref/Entities/Permissions/ServerPermissions.cs b/ref/Entities/Permissions/ServerPermissions.cs new file mode 100644 index 000000000..fe85c07dd --- /dev/null +++ b/ref/Entities/Permissions/ServerPermissions.cs @@ -0,0 +1,55 @@ +namespace Discord +{ + public struct ServerPermissions + { + public static ServerPermissions None { get; } + public static ServerPermissions All { get; } + + public uint RawValue { get; } + + public bool CreateInstantInvite { get; } + public bool BanMembers { get; } + public bool KickMembers { get; } + public bool ManageRoles { get; } + public bool ManageChannels { get; } + public bool ManageServer { get; } + + public bool ReadMessages { get; } + public bool SendMessages { get; } + public bool SendTTSMessages { get; } + public bool ManageMessages { get; } + public bool EmbedLinks { get; } + public bool AttachFiles { get; } + public bool ReadMessageHistory { get; } + public bool MentionEveryone { get; } + + public bool Connect { get; } + public bool Speak { get; } + public bool MuteMembers { get; } + public bool DeafenMembers { get; } + public bool MoveMembers { get; } + public bool UseVoiceActivation { get; } + + public ServerPermissions(bool? createInstantInvite = null, bool? manageRoles = null, + bool? kickMembers = null, bool? banMembers = null, bool? manageChannel = null, bool? manageServer = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null) + : this() + { + } + public ServerPermissions(uint rawValue) + : this() + { + } + + public ServerPermissions Modify(bool? createInstantInvite = null, bool? manageRoles = null, + bool? kickMembers = null, bool? banMembers = null, bool? manageChannel = null, bool? manageServer = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null) + => default(ServerPermissions); + } +} diff --git a/ref/Entities/Profile.cs b/ref/Entities/Profile.cs new file mode 100644 index 000000000..aa61e51b2 --- /dev/null +++ b/ref/Entities/Profile.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public class Profile : IEntity + { + public ulong Id { get; } + public DiscordClient Discord { get; } + public EntityState State { get; } + + public string AvatarId { get; } + public string AvatarUrl { get; } + public ushort Discriminator { get; } + public string CurrentGame { get; } + public UserStatus Status { get; } + public string Mention { get; } + public string Email { get; } + public bool? IsVerified { get; } + + public string Name { get; set; } + + public Task Update() => null; + public Task Delete() => null; + } +} diff --git a/ref/Entities/Region.cs b/ref/Entities/Region.cs new file mode 100644 index 000000000..fbb801eaa --- /dev/null +++ b/ref/Entities/Region.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public class Region + { + public string Id { get; } + public string Name { get; } + public string Hostname { get; } + public int Port { get; } + public bool Vip { get; } + } +} diff --git a/ref/Entities/Role.cs b/ref/Entities/Role.cs new file mode 100644 index 000000000..f5155db2e --- /dev/null +++ b/ref/Entities/Role.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public class Role : IEntity, IMentionable + { + public ulong Id { get; } + public DiscordClient Discord { get; } + public EntityState State { get; } + + public Server Server { get; } + + public string Name { get; } + public bool IsHoisted { get; } + public int Position { get; } + public bool IsManaged { get; } + public ServerPermissions Permissions { get; } + public Color Color { get; } + + public bool IsEveryone { get; } + public IEnumerable Members { get; } + + public string Mention { get; } + + public Task Update() => null; + public Task Delete() => null; + } +} diff --git a/ref/Entities/Server.cs b/ref/Entities/Server.cs new file mode 100644 index 000000000..a9078cb4b --- /dev/null +++ b/ref/Entities/Server.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public class Server : IEntity + { + public class Emoji + { + public string Id { get; } + public string Name { get; } + public bool IsManaged { get; } + public bool RequireColons { get; } + public IEnumerable Roles { get; } + } + + public ulong Id { get; } + public DiscordClient Discord { get; } + public EntityState State { get; } + + public ServerUser CurrentUser { get; } + public string IconId { get; } + public string SplashId { get; } + public string IconUrl { get; } + public string SplashUrl { get; } + public int ChannelCount { get; } + public int UserCount { get; } + public int RoleCount { get; } + public TextChannel DefaultChannel { get; } + public Role EveryoneRole { get; } + public IEnumerable Features { get; } + public IEnumerable CustomEmojis { get; } + public IEnumerable Channels { get; } + public IEnumerable TextChannels { get; } + public IEnumerable VoiceChannels { get; } + public IEnumerable Users { get; } + public IEnumerable Roles { get; } + + public string Name { get; set; } + public Region Region { get; set; } + public int AFKTimeout { get; set; } + public DateTime JoinedAt { get; set; } + public ServerUser Owner { get; set; } + public VoiceChannel AFKChannel { get; set; } + + public Task GetChannel(ulong id) => null; + public Task GetChannel(string mention) => null; + public Task GetRole(ulong id) => null; + public Task GetUser(ulong id) => null; + public Task GetUser(string name, ushort discriminator) => null; + public Task GetUser(string mention) => null; + public Task> GetBans() => null; + public Task> GetInvites() => null; + + public Task CreateTextChannel(string name) => null; + public Task CreateVoiceChannel(string name) => null; + public Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null; + public Task CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) => null; + + public Task PruneUsers(int days = 30, bool simulate = false) => null; + + public Task Update() => null; + public Task Leave() => null; + public Task Delete() => null; + } +} diff --git a/ref/Entities/Users/IUser.cs b/ref/Entities/Users/IUser.cs new file mode 100644 index 000000000..02dd2d85b --- /dev/null +++ b/ref/Entities/Users/IUser.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public interface IUser : IEntity, IMentionable + { + bool IsPrivate { get; } + + string Name { get; } + ushort Discriminator { get; } + bool IsBot { get; } + string AvatarId { get; } + string AvatarUrl { get; } + string CurrentGame { get; } + UserStatus Status { get; } + + Task GetPrivateChannel(); + } +} diff --git a/ref/Entities/Users/PrivateUser.cs b/ref/Entities/Users/PrivateUser.cs new file mode 100644 index 000000000..a6cc9d6e7 --- /dev/null +++ b/ref/Entities/Users/PrivateUser.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; + +namespace Discord +{ + //TODO: Should this be linked directly to the Profile when it represents us, instead of maintaining a cache of values? + public class PrivateUser : IUser + { + /// + public EntityState State { get; internal set; } + /// + public ulong Id { get; } + /// Returns the private channel for this user. + public PrivateChannel Channel { get; } + + /// + bool IUser.IsPrivate => true; + + /// + public string Name { get; } + /// + public ushort Discriminator { get; } + /// + public bool IsBot { get; } + /// + public string AvatarId { get; } + /// + public string CurrentGame { get; } + /// + public UserStatus Status { get; } + + /// + public DiscordClient Discord => Channel.Discord; + /// + public string AvatarUrl { get; } + /// + public string Mention { get; } + + /// + Task IUser.GetPrivateChannel() => Task.FromResult(Channel); + + public Task Update() => null; + } +} diff --git a/ref/Entities/Users/ServerUser.cs b/ref/Entities/Users/ServerUser.cs new file mode 100644 index 000000000..4ff86f67a --- /dev/null +++ b/ref/Entities/Users/ServerUser.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public class ServerUser : IUser + { + /// + public EntityState State { get; } + /// + public ulong Id { get; } + /// Returns the private channel for this user. + public Server Server { get; } + + /// + bool IUser.IsPrivate => false; + + /// + public string Name { get; } + /// + public ushort Discriminator { get; } + /// + public bool IsBot { get; } + /// + public string AvatarId { get; } + /// + public string CurrentGame { get; } + /// + public UserStatus Status { get; } + /// + public DateTime JoinedAt { get; } + /// + public IReadOnlyList Roles { get; } + + /// Returns true if this user has marked themselves as muted. + public bool IsSelfMuted { get; } + /// Returns true if this user has marked themselves as deafened. + public bool IsSelfDeafened { get; } + /// Returns true if the server is blocking audio from this user. + public bool IsServerMuted { get; } + /// Returns true if the server is blocking audio to this user. + public bool IsServerDeafened { get; } + /// Returns true if the server is temporarily blocking audio to/from this user. + public bool IsServerSuppressed { get; } + /// Gets this user's current voice channel. + public VoiceChannel VoiceChannel { get; } + + /// + public DiscordClient Discord { get; } + /// + public string AvatarUrl { get; } + /// + public string Mention { get; } + + public ServerPermissions ServerPermissions { get; } + + public ChannelPermissions GetPermissions(IPublicChannel channel) => default(ChannelPermissions); + /// + public Task GetPrivateChannel() => null; + public Task> GetChannels() => null; + + public bool HasRole(Role role) => false; + + public Task AddRoles(params Role[] roles) => null; + public Task RemoveRoles(params Role[] roles) => null; + + public Task Update() => null; + public Task Kick() => null; + public Task Ban(int pruneDays = 0) => null; + public Task Unban() => null; + } +} \ No newline at end of file diff --git a/ref/Enums/ChannelType.cs b/ref/Enums/ChannelType.cs new file mode 100644 index 000000000..5ebbf3aa6 --- /dev/null +++ b/ref/Enums/ChannelType.cs @@ -0,0 +1,13 @@ +using System; + +namespace Discord +{ + [Flags] + public enum ChannelType : byte + { + Public = 0x01, + Private = 0x02, + Text = 0x10, + Voice = 0x20 + } +} diff --git a/ref/Enums/ConnectionState.cs b/ref/Enums/ConnectionState.cs new file mode 100644 index 000000000..dfd4ac9eb --- /dev/null +++ b/ref/Enums/ConnectionState.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + public enum ConnectionState + { + Disconnected, + Connecting, + Connected, + Disconnecting + } +} diff --git a/ref/Enums/EntityState.cs b/ref/Enums/EntityState.cs new file mode 100644 index 000000000..6ae71e4a3 --- /dev/null +++ b/ref/Enums/EntityState.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + public enum EntityState : byte + { + /// Object is not attached to a cache manager nor receiving live updates. + Detached = 0, + /// Object is attached to a cache manager and receiving live updates. + Attached, + /// Object was deleted. + Deleted, + /// Object is currently waiting to be created. + Queued, + /// Object's creation was aborted. + Aborted, + /// Object's creation failed. + Failed + } +} diff --git a/ref/Enums/ImageType.cs b/ref/Enums/ImageType.cs new file mode 100644 index 000000000..738c67a3d --- /dev/null +++ b/ref/Enums/ImageType.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum ImageType + { + None, + Jpeg, + Png + } +} diff --git a/ref/Enums/LogSeverity.cs b/ref/Enums/LogSeverity.cs new file mode 100644 index 000000000..785b0ef46 --- /dev/null +++ b/ref/Enums/LogSeverity.cs @@ -0,0 +1,12 @@ +namespace Discord +{ + public enum LogSeverity + { + Critical = 0, + Error = 1, + Warning = 2, + Info = 3, + Verbose = 4, + Debug = 5 + } +} diff --git a/ref/Enums/PermValue.cs b/ref/Enums/PermValue.cs new file mode 100644 index 000000000..fe048b016 --- /dev/null +++ b/ref/Enums/PermValue.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum PermValue + { + Allow, + Deny, + Inherit + } +} diff --git a/ref/Enums/PermissionTarget.cs b/ref/Enums/PermissionTarget.cs new file mode 100644 index 000000000..96595fb69 --- /dev/null +++ b/ref/Enums/PermissionTarget.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum PermissionTarget + { + Role, + User + } +} diff --git a/ref/Enums/Relative.cs b/ref/Enums/Relative.cs new file mode 100644 index 000000000..aade047d1 --- /dev/null +++ b/ref/Enums/Relative.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum Relative + { + Before, + After + } +} diff --git a/ref/Enums/UserStatus.cs b/ref/Enums/UserStatus.cs new file mode 100644 index 000000000..f2fdfda7c --- /dev/null +++ b/ref/Enums/UserStatus.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum UserStatus + { + Online, + Idle, + Offline + } +} diff --git a/ref/Events/ChannelEventArgs.cs b/ref/Events/ChannelEventArgs.cs new file mode 100644 index 000000000..583075e08 --- /dev/null +++ b/ref/Events/ChannelEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord +{ + public class ChannelEventArgs : EventArgs + { + public IChannel Channel => null; + } +} diff --git a/ref/Events/ChannelUpdatedEventArgs.cs b/ref/Events/ChannelUpdatedEventArgs.cs new file mode 100644 index 000000000..bcd809521 --- /dev/null +++ b/ref/Events/ChannelUpdatedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + public class ChannelUpdatedEventArgs : EventArgs + { + public IChannel Before => null; + public IChannel After => null; + } +} diff --git a/ref/Events/DisconnectedEventArgs.cs b/ref/Events/DisconnectedEventArgs.cs new file mode 100644 index 000000000..616f3f09d --- /dev/null +++ b/ref/Events/DisconnectedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + public class DisconnectedEventArgs : EventArgs + { + public bool WasUnexpected => false; + public Exception Exception => null; + } +} diff --git a/ref/Events/LogMessageEventArgs.cs b/ref/Events/LogMessageEventArgs.cs new file mode 100644 index 000000000..7dec182d1 --- /dev/null +++ b/ref/Events/LogMessageEventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord +{ + public class LogMessageEventArgs : EventArgs + { + public LogSeverity Severity => default(LogSeverity); + public string Source => null; + public string Message => null; + public Exception Exception => null; + } +} diff --git a/ref/Events/MessageEventArgs.cs b/ref/Events/MessageEventArgs.cs new file mode 100644 index 000000000..f75c7f1a8 --- /dev/null +++ b/ref/Events/MessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord +{ + public class MessageEventArgs : EventArgs + { + public Message Message => null; + public IUser User => null; + public ITextChannel Channel => null; + } +} diff --git a/ref/Events/MessageUpdatedEventArgs.cs b/ref/Events/MessageUpdatedEventArgs.cs new file mode 100644 index 000000000..d323bf809 --- /dev/null +++ b/ref/Events/MessageUpdatedEventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord +{ + public class MessageUpdatedEventArgs : EventArgs + { + public Message Before => null; + public Message After => null; + public IUser User => null; + public ITextChannel Channel => null; + } +} diff --git a/ref/Events/ProfileUpdatedEventArgs.cs b/ref/Events/ProfileUpdatedEventArgs.cs new file mode 100644 index 000000000..dba55af3b --- /dev/null +++ b/ref/Events/ProfileUpdatedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + public class ProfileUpdatedEventArgs : EventArgs + { + public Profile Before => null; + public Profile After => null; + } +} diff --git a/ref/Events/RoleEventArgs.cs b/ref/Events/RoleEventArgs.cs new file mode 100644 index 000000000..db1d09cbc --- /dev/null +++ b/ref/Events/RoleEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + public class RoleEventArgs : EventArgs + { + public Role Role => null; + public Server Server => null; + } +} diff --git a/ref/Events/RoleUpdatedEventArgs.cs b/ref/Events/RoleUpdatedEventArgs.cs new file mode 100644 index 000000000..1fa0f2a81 --- /dev/null +++ b/ref/Events/RoleUpdatedEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord +{ + public class RoleUpdatedEventArgs : EventArgs + { + public Role Before => null; + public Role After => null; + public Server Server => null; + } +} diff --git a/ref/Events/ServerEventArgs.cs b/ref/Events/ServerEventArgs.cs new file mode 100644 index 000000000..b06993de9 --- /dev/null +++ b/ref/Events/ServerEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord +{ + public class ServerEventArgs : EventArgs + { + public Server Server => null; + } +} diff --git a/ref/Events/ServerUpdatedEventArgs.cs b/ref/Events/ServerUpdatedEventArgs.cs new file mode 100644 index 000000000..1e05f1721 --- /dev/null +++ b/ref/Events/ServerUpdatedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + public class ServerUpdatedEventArgs : EventArgs + { + public Server Before => null; + public Server After => null; + } +} diff --git a/ref/Events/TypingEventArgs.cs b/ref/Events/TypingEventArgs.cs new file mode 100644 index 000000000..f45313687 --- /dev/null +++ b/ref/Events/TypingEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + public class TypingEventArgs + { + public ITextChannel Channel { get; } + public IUser User { get; } + + public TypingEventArgs(ITextChannel channel, IUser user) + { + Channel = channel; + User = user; + } + } +} diff --git a/ref/Events/UserEventArgs.cs b/ref/Events/UserEventArgs.cs new file mode 100644 index 000000000..f1cce29fc --- /dev/null +++ b/ref/Events/UserEventArgs.cs @@ -0,0 +1,8 @@ +using System; +namespace Discord +{ + public class UserEventArgs : EventArgs + { + public IUser User => null; + } +} diff --git a/ref/Events/UserUpdatedEventArgs.cs b/ref/Events/UserUpdatedEventArgs.cs new file mode 100644 index 000000000..c45c60701 --- /dev/null +++ b/ref/Events/UserUpdatedEventArgs.cs @@ -0,0 +1,9 @@ +using System; +namespace Discord +{ + public class UserUpdatedEventArgs : EventArgs + { + public IUser Before => null; + public IUser After => null; + } +} diff --git a/ref/Format.cs b/ref/Format.cs new file mode 100644 index 000000000..e30931ae9 --- /dev/null +++ b/ref/Format.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + public static class Format + { + public static string Escape(string text) => null; + + public static string Bold(string text, bool escape = true) => null; + public static string Italics(string text, bool escape = true) => null; + public static string Underline(string text, bool escape = true) => null; + public static string Strikeout(string text, bool escape = true) => null; + + public static string Code(string text, string language = null) => null; + } +} diff --git a/ref/ILogger.cs b/ref/ILogger.cs new file mode 100644 index 000000000..a3123edc9 --- /dev/null +++ b/ref/ILogger.cs @@ -0,0 +1,30 @@ +using System; + +namespace Discord.Logging +{ + public interface ILogger + { + LogSeverity Level { get; } + + void Log(LogSeverity severity, string message, Exception exception = null); + void Error(string message, Exception exception = null); + void Error(Exception exception); + void Warning(string message, Exception exception = null); + void Warning(Exception exception); + void Info(string message, Exception exception = null); + void Info(Exception exception); + void Verbose(string message, Exception exception = null); + void Verbose(Exception exception); + void Debug(string message, Exception exception = null); + void Debug(Exception exception); + +#if DOTNET5_4 + void Log(LogSeverity severity, FormattableString message, Exception exception = null); + void Error(FormattableString message, Exception exception = null); + void Warning(FormattableString message, Exception exception = null); + void Info(FormattableString message, Exception exception = null); + void Verbose(FormattableString message, Exception exception = null); + void Debug(FormattableString message, Exception exception = null); +#endif + } +} diff --git a/ref/MessageQueue.cs b/ref/MessageQueue.cs new file mode 100644 index 000000000..5f56abd1e --- /dev/null +++ b/ref/MessageQueue.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public class MessageQueue + { + public int Count { get; } + + public void Clear() { } + } +} diff --git a/ref/Net/HttpException.cs b/ref/Net/HttpException.cs new file mode 100644 index 000000000..3704ffb83 --- /dev/null +++ b/ref/Net/HttpException.cs @@ -0,0 +1,16 @@ +using System; +using System.Net; + +namespace Discord.Net +{ + public class HttpException : Exception + { + public HttpStatusCode StatusCode { get; } + + public HttpException(HttpStatusCode statusCode) + : base($"The server responded with error {(int)statusCode} ({statusCode})") + { + StatusCode = statusCode; + } + } +} diff --git a/ref/Net/Rest/CompletedRequestEventArgs.cs b/ref/Net/Rest/CompletedRequestEventArgs.cs new file mode 100644 index 000000000..ed9d1673f --- /dev/null +++ b/ref/Net/Rest/CompletedRequestEventArgs.cs @@ -0,0 +1,17 @@ +namespace Discord.Net.Rest +{ + public class CompletedRequestEventArgs : RequestEventArgs + { + public object Response { get; set; } + public string ResponseJson { get; set; } + public double Milliseconds { get; set; } + + public CompletedRequestEventArgs(IRestRequest request, object response, string responseJson, double milliseconds) + : base(request) + { + Response = response; + ResponseJson = responseJson; + Milliseconds = milliseconds; + } + } +} diff --git a/ref/Net/Rest/IRestClient.cs b/ref/Net/Rest/IRestClient.cs new file mode 100644 index 000000000..83c0405c7 --- /dev/null +++ b/ref/Net/Rest/IRestClient.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Rest +{ + public interface IRestClient + { + event EventHandler SendingRequest; + event EventHandler SentRequest; + + CancellationToken CancelToken { get; } + string Token { get; } + + Task Send(IRestRequest request) + where ResponseT : class; + Task Send(IRestRequest request); + + Task Send(IRestFileRequest request) + where ResponseT : class; + Task Send(IRestFileRequest request); + } +} diff --git a/ref/Net/Rest/IRestClientProvider.cs b/ref/Net/Rest/IRestClientProvider.cs new file mode 100644 index 000000000..cb22a7474 --- /dev/null +++ b/ref/Net/Rest/IRestClientProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Discord.Net.Rest +{ + public interface IRestClientProvider + { + IRestClient Create(string baseUrl, CancellationToken cancelToken); + } +} diff --git a/ref/Net/Rest/IRestRequest.cs b/ref/Net/Rest/IRestRequest.cs new file mode 100644 index 000000000..9d46e645f --- /dev/null +++ b/ref/Net/Rest/IRestRequest.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Discord.Net.Rest +{ + public interface IRestRequest + { + string Method { get; } + string Endpoint { get; } + object Payload { get; } + } + public interface IRestRequest : IRestRequest + where ResponseT : class + { + } + + public interface IRestFileRequest : IRestRequest + { + string Filename { get; } + Stream Stream { get; } + } + public interface IRestFileRequest : IRestFileRequest, IRestRequest + where ResponseT : class + { + } +} diff --git a/ref/Net/Rest/RequestEventArgs.cs b/ref/Net/Rest/RequestEventArgs.cs new file mode 100644 index 000000000..cac734fc6 --- /dev/null +++ b/ref/Net/Rest/RequestEventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord.Net.Rest +{ + public class RequestEventArgs : EventArgs + { + public IRestRequest Request { get; set; } + public bool Cancel { get; set; } + + public RequestEventArgs(IRestRequest request) { } + } +} diff --git a/ref/Net/TimeoutException.cs b/ref/Net/TimeoutException.cs new file mode 100644 index 000000000..d1a644049 --- /dev/null +++ b/ref/Net/TimeoutException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Net +{ + public class TimeoutException : OperationCanceledException + { + public TimeoutException() { } + } +} diff --git a/ref/Net/WebSocketException.cs b/ref/Net/WebSocketException.cs new file mode 100644 index 000000000..df6377e13 --- /dev/null +++ b/ref/Net/WebSocketException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord.Net +{ + public class WebSocketException : Exception + { + public int Code { get; } + public string Reason { get; } + + public WebSocketException(int code, string reason) { } + } +} diff --git a/ref/Net/WebSockets/BinaryMessageEventArgs.cs b/ref/Net/WebSockets/BinaryMessageEventArgs.cs new file mode 100644 index 000000000..3fd4425fa --- /dev/null +++ b/ref/Net/WebSockets/BinaryMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class BinaryMessageEventArgs : EventArgs + { + public byte[] Data { get; } + + public BinaryMessageEventArgs(byte[] data) { } + } +} diff --git a/ref/Net/WebSockets/GatewaySocket.cs b/ref/Net/WebSockets/GatewaySocket.cs new file mode 100644 index 000000000..e8f2ddd3d --- /dev/null +++ b/ref/Net/WebSockets/GatewaySocket.cs @@ -0,0 +1,28 @@ +using Discord.Net.Rest; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public class GatewaySocket + { + public string SessionId { get; } + + public event EventHandler ReceivedDispatch = delegate { }; + + public Task Connect(IRestClient rest, CancellationToken parentCancelToken) => null; + public Task Disconnect() => null; + + public void SendIdentify(string token) { } + + public void SendResume() { } + public void SendHeartbeat() { } + public void SendUpdateStatus(long? idleSince, string gameName) { } + public void SendUpdateVoice(ulong? serverId, ulong? channelId, bool isSelfMuted, bool isSelfDeafened) { } + public void SendRequestMembers(IEnumerable serverId, string query, int limit) { } + + public void WaitForConnection(CancellationToken cancelToken) { } + } +} diff --git a/ref/Net/WebSockets/IWebSocket.cs b/ref/Net/WebSockets/IWebSocket.cs new file mode 100644 index 000000000..06a274305 --- /dev/null +++ b/ref/Net/WebSockets/IWebSocket.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; + +namespace Discord.Net.WebSockets +{ + public interface IWebSocket + { + CancellationToken CancelToken { get; } + ConnectionState State { get; } + string Host { get; set; } + + event EventHandler Connected; + event EventHandler Disconnected; + } +} diff --git a/ref/Net/WebSockets/IWebSocketEngine.cs b/ref/Net/WebSockets/IWebSocketEngine.cs new file mode 100644 index 000000000..68f31f12b --- /dev/null +++ b/ref/Net/WebSockets/IWebSocketEngine.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public interface IWebSocketEngine + { + event EventHandler BinaryMessage; + event EventHandler TextMessage; + + Task Connect(string host, CancellationToken cancelToken); + Task Disconnect(); + void QueueMessage(string message); + IEnumerable GetTasks(CancellationToken cancelToken); + } +} diff --git a/ref/Net/WebSockets/IWebSocketProvider.cs b/ref/Net/WebSockets/IWebSocketProvider.cs new file mode 100644 index 000000000..20f7559be --- /dev/null +++ b/ref/Net/WebSockets/IWebSocketProvider.cs @@ -0,0 +1,9 @@ +using System.Threading; + +namespace Discord.Net.WebSockets +{ + public interface IWebSocketProvider + { + IWebSocket Create(CancellationToken cancelToken); + } +} diff --git a/ref/Net/WebSockets/TextMessageEventArgs.cs b/ref/Net/WebSockets/TextMessageEventArgs.cs new file mode 100644 index 000000000..e4e186044 --- /dev/null +++ b/ref/Net/WebSockets/TextMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class TextMessageEventArgs : EventArgs + { + public string Message { get; } + + public TextMessageEventArgs(string msg) { Message = msg; } + } +} diff --git a/ref/Net/WebSockets/WebSocketEventEventArgs.cs b/ref/Net/WebSockets/WebSocketEventEventArgs.cs new file mode 100644 index 000000000..676c0ba6e --- /dev/null +++ b/ref/Net/WebSockets/WebSocketEventEventArgs.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace Discord.Net.WebSockets +{ + public class WebSocketEventEventArgs : EventArgs + { + public string Type { get; } + public JToken Payload { get; } + } +} diff --git a/ref/project.json b/ref/project.json new file mode 100644 index 000000000..565bc2e86 --- /dev/null +++ b/ref/project.json @@ -0,0 +1,81 @@ +{ + "version": "0.9.0-rc3-3", + "description": "An unofficial .Net API wrapper for the Discord client.", + "authors": [ + "RogueException" + ], + "tags": [ + "discord", + "discordapp" + ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + }, + "compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ], + + "compilationOptions": { + "allowUnsafe": true, + "warningsAsErrors": true + }, + + "configurations": { + "TestResponses": { + "compilationOptions": { + "define": [ + "DEBUG", + "TRACE", + "TEST_RESPONSES" + ] + } + } + }, + + "dependencies": { + "Newtonsoft.Json": "8.0.1", + "Nito.AsyncEx": "3.0.1" + }, + + "frameworks": { + "dotnet5.4": { + "dependencies": { + "System.Collections": "4.0.11-beta-23516", + "System.Collections.Concurrent": "4.0.11-beta-23516", + "System.Dynamic.Runtime": "4.0.11-beta-23516", + "System.IO.FileSystem": "4.0.1-beta-23516", + "System.IO.Compression": "4.1.0-beta-23516", + "System.Linq": "4.0.1-beta-23516", + "System.Net.Http": "4.0.1-beta-23516", + "System.Net.NameResolution": "4.0.0-beta-23516", + "System.Net.Sockets": "4.1.0-beta-23409", + "System.Net.Requests": "4.0.11-beta-23516", + "System.Net.WebSockets.Client": "4.0.0-beta-23516", + "System.Reflection": "4.1.0-beta-23516", + "System.Reflection.Emit.Lightweight": "4.0.1-beta-23516", + "System.Runtime.InteropServices": "4.0.21-beta-23516", + "System.Runtime.Serialization.Primitives": "4.1.0-beta-23516", + "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", + "System.Text.RegularExpressions": "4.0.11-beta-23516", + "System.Threading": "4.0.11-beta-23516" + } + }, + "net45": { + "frameworkAssemblies": { + "System.Runtime": { + "type": "build", + "version": "" + }, + "System.Threading.Tasks": { + "type": "build", + "version": "" + } + }, + "dependencies": { + "WebSocket4Net": "0.14.1", + "RestSharp": "105.2.3" + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj b/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj deleted file mode 100644 index f7326b4a9..000000000 --- a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj +++ /dev/null @@ -1,128 +0,0 @@ - - - - - Debug - AnyCPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3} - Library - Properties - Discord.Audio - Discord.Net.Audio - 512 - v4.5 - False - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET45 - prompt - 4 - 6 - true - true - - - pdbonly - true - bin\Release\ - TRACE;NET45 - prompt - 4 - true - 6 - true - - - - - - - AudioClient.cs - - - AudioExtensions.cs - - - AudioMode.cs - - - AudioService.cs - - - AudioServiceConfig.cs - - - IAudioClient.cs - - - InternalFrameEventArgs.cs - - - InternalIsSpeakingEventArgs.cs - - - Net\VoiceSocket.cs - - - Opus\OpusConverter.cs - - - Opus\OpusDecoder.cs - - - Opus\OpusEncoder.cs - - - Sodium\SecretBox.cs - - - UserIsTalkingEventArgs.cs - - - VirtualClient.cs - - - VoiceBuffer.cs - - - VoiceDisconnectedEventArgs.cs - - - - - - {8d71a857-879a-4a10-859e-5ff824ed6688} - Discord.Net - - - - - libsodium.dll - Always - - - opus.dll - Always - - - - - - project.json - - - - - - \ No newline at end of file diff --git a/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs deleted file mode 100644 index c2c8aa3a7..000000000 --- a/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Discord.Net.Audio")] -[assembly: AssemblyDescription("A Discord.Net extension adding voice support.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("RogueException")] -[assembly: AssemblyProduct("Discord.Net.Modules")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] -[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] - -[assembly: AssemblyVersion("0.9.0.0")] -[assembly: AssemblyFileVersion("0.9.0.0")] - diff --git a/src/Discord.Net.Audio.Net45/project.json b/src/Discord.Net.Audio.Net45/project.json deleted file mode 100644 index a9a422249..000000000 --- a/src/Discord.Net.Audio.Net45/project.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "dependencies": { - "Newtonsoft.Json": "8.0.1", - "Nito.AsyncEx": "3.0.1" - }, - "frameworks": { - "net45": { } - }, - "runtimes": { - "win": { }, - "win-x86": { }, - "win-x64": { } - } -} \ No newline at end of file diff --git a/src/Discord.Net.Audio/AudioClient.cs b/src/Discord.Net.Audio/AudioClient.cs index 46474f6a4..00aacea12 100644 --- a/src/Discord.Net.Audio/AudioClient.cs +++ b/src/Discord.Net.Audio/AudioClient.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json; using Nito.AsyncEx; using System; using System.Diagnostics; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -15,35 +14,6 @@ namespace Discord.Audio { internal class AudioClient : IAudioClient { - private class OutStream : Stream - { - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - - private readonly AudioClient _client; - - internal OutStream(AudioClient client) - { - _client = client; - } - - public override long Length { get { throw new InvalidOperationException(); } } - public override long Position - { - get { throw new InvalidOperationException(); } - set { throw new InvalidOperationException(); } - } - public override void Flush() { throw new InvalidOperationException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } - public override void SetLength(long value) { throw new InvalidOperationException(); } - public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); } - public override void Write(byte[] buffer, int offset, int count) - { - _client.Send(buffer, offset, count); - } - } - private readonly DiscordConfig _config; private readonly AsyncLock _connectionLock; private readonly TaskManager _taskManager; @@ -58,20 +28,18 @@ namespace Discord.Audio public GatewaySocket GatewaySocket { get; } public VoiceSocket VoiceSocket { get; } public JsonSerializer Serializer { get; } - public Stream OutputStream { get; } public CancellationToken CancelToken { get; private set; } public string SessionId => GatewaySocket.SessionId; public ConnectionState State => VoiceSocket.State; public Server Server => VoiceSocket.Server; - public Channel Channel => VoiceSocket.Channel; + public VoiceChannel Channel => VoiceSocket.Channel; public AudioClient(DiscordClient client, Server server, int id) { Id = id; - _config = client.Config; - Service = client.Services.Get(); + Service = client.GetService(); Config = Service.Config; Serializer = client.Serializer; _gatewayState = (int)ConnectionState.Disconnected; @@ -87,6 +55,25 @@ namespace Discord.Audio //Networking if (Config.EnableMultiserver) { + //TODO: We can remove this hack when official API launches + var baseConfig = client.Config; + var builder = new DiscordConfigBuilder + { + AppName = baseConfig.AppName, + AppUrl = baseConfig.AppUrl, + AppVersion = baseConfig.AppVersion, + CacheToken = baseConfig.CacheDir != null, + ConnectionTimeout = baseConfig.ConnectionTimeout, + EnablePreUpdateEvents = false, + FailedReconnectDelay = baseConfig.FailedReconnectDelay, + LargeThreshold = 1, + LogLevel = baseConfig.LogLevel, + MessageCacheSize = 0, + ReconnectDelay = baseConfig.ReconnectDelay, + UsePermissionsCache = false + }; + _config = builder.Build(); + ClientAPI = new JsonRestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}")); GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}")); GatewaySocket.Connected += (s, e) => @@ -96,11 +83,13 @@ namespace Discord.Audio }; } else + { + _config = client.Config; GatewaySocket = client.GatewaySocket; + } GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); VoiceSocket.Server = server; - OutputStream = new OutStream(this); } public async Task Connect() @@ -195,7 +184,7 @@ namespace Discord.Audio _gatewayState = (int)ConnectionState.Disconnected; } - public async Task Join(Channel channel) + public async Task Join(VoiceChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel.Type != ChannelType.Voice) @@ -209,7 +198,7 @@ namespace Discord.Audio SendVoiceUpdate(channel.Server.Id, channel.Id); using (await _connectionLock.LockAsync().ConfigureAwait(false)) - await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)); + await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)).ConfigureAwait(false); } private async void OnReceivedEvent(WebSocketEventEventArgs e) @@ -227,7 +216,7 @@ namespace Discord.Audio await Disconnect().ConfigureAwait(false); else { - var channel = Service.Client.GetChannel(data.ChannelId.Value); + var channel = Service.Client.GetChannel(data.ChannelId.Value) as VoiceChannel; if (channel != null) VoiceSocket.Channel = channel; else diff --git a/src/Discord.Net.Audio/AudioExtensions.cs b/src/Discord.Net.Audio/AudioExtensions.cs index 50f508ff5..7def445a6 100644 --- a/src/Discord.Net.Audio/AudioExtensions.cs +++ b/src/Discord.Net.Audio/AudioExtensions.cs @@ -7,20 +7,20 @@ namespace Discord.Audio { public static DiscordClient UsingAudio(this DiscordClient client, AudioServiceConfig config = null) { - client.Services.Add(new AudioService(config)); + client.AddService(new AudioService(config)); return client; } public static DiscordClient UsingAudio(this DiscordClient client, Action configFunc = null) { var builder = new AudioServiceConfigBuilder(); configFunc(builder); - client.Services.Add(new AudioService(builder)); + client.AddService(new AudioService(builder)); return client; } - public static Task JoinAudio(this Channel channel) => channel.Client.Services.Get().Join(channel); - public static Task LeaveAudio(this Channel channel) => channel.Client.Services.Get().Leave(channel); - public static Task LeaveAudio(this Server server) => server.Client.Services.Get().Leave(server); - public static IAudioClient GetAudioClient(Server server) => server.Client.Services.Get().GetClient(server); + public static Task JoinAudio(this VoiceChannel channel) => channel.Client.GetService().Join(channel); + public static Task LeaveAudio(this VoiceChannel channel) => channel.Client.GetService().Leave(channel); + public static Task LeaveAudio(this Server server) => server.Client.GetService().Leave(server); + public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService().GetClient(server); } } diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs index 3de3531ae..e44a4a1ce 100644 --- a/src/Discord.Net.Audio/AudioService.cs +++ b/src/Discord.Net.Audio/AudioService.cs @@ -113,7 +113,7 @@ namespace Discord.Audio } } - public async Task Join(Channel channel) + public async Task Join(VoiceChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); @@ -163,8 +163,8 @@ namespace Discord.Audio } public Task Leave(Server server) => Leave(server, null); - public Task Leave(Channel channel) => Leave(channel.Server, channel); - private async Task Leave(Server server, Channel channel) + public Task Leave(VoiceChannel channel) => Leave(channel.Server, channel); + private async Task Leave(Server server, VoiceChannel channel) { if (server == null) throw new ArgumentNullException(nameof(server)); diff --git a/src/Discord.Net.Audio/IAudioClient.cs b/src/Discord.Net.Audio/IAudioClient.cs index 71c8783d5..a986fad7d 100644 --- a/src/Discord.Net.Audio/IAudioClient.cs +++ b/src/Discord.Net.Audio/IAudioClient.cs @@ -15,11 +15,9 @@ namespace Discord.Audio /// Gets the current state of this client. ConnectionState State { get; } /// Gets the channel this client is currently a member of. - Channel Channel { get; } + VoiceChannel Channel { get; } /// Gets the server this client is bound to. Server Server { get; } - /// Gets a stream object that wraps the Send() function. - Stream OutputStream { get; } /// Gets a cancellation token that triggers when the client is manually disconnected. CancellationToken CancelToken { get; } @@ -31,7 +29,7 @@ namespace Discord.Audio VoiceSocket VoiceSocket { get; } /// Moves the client to another channel on the same server. - Task Join(Channel channel); + Task Join(VoiceChannel channel); /// Disconnects from the Discord server, canceling any pending requests. Task Disconnect(); diff --git a/src/Discord.Net.Audio/Net/VoiceSocket.cs b/src/Discord.Net.Audio/Net/VoiceSocket.cs index adb583f1d..9ee5c60e0 100644 --- a/src/Discord.Net.Audio/Net/VoiceSocket.cs +++ b/src/Discord.Net.Audio/Net/VoiceSocket.cs @@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets public string Token { get; internal set; } public Server Server { get; internal set; } - public Channel Channel { get; internal set; } + public VoiceChannel Channel { get; internal set; } public int Ping => _ping; internal VoiceBuffer OutputBuffer => _sendBuffer; @@ -371,7 +371,7 @@ namespace Discord.Net.WebSockets break; } } - await _udp.SendAsync(pingPacket, pingPacket.Length); + await _udp.SendAsync(pingPacket, pingPacket.Length).ConfigureAwait(false); nextPingTicks = currentTicks + 5 * ticksPerSeconds; } } @@ -395,7 +395,7 @@ namespace Discord.Net.WebSockets //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken private async Task WatcherAsync() { - await CancelToken.Wait(); + await CancelToken.Wait().ConfigureAwait(false); _udp.Close(); } #endif diff --git a/src/Discord.Net.Audio/VirtualClient.cs b/src/Discord.Net.Audio/VirtualClient.cs index 12d285a0d..9c8100e47 100644 --- a/src/Discord.Net.Audio/VirtualClient.cs +++ b/src/Discord.Net.Audio/VirtualClient.cs @@ -16,8 +16,7 @@ namespace Discord.Audio public string SessionId => _client.Server == Server ? _client.SessionId : null; public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected; - public Channel Channel => _client.Server == Server ? _client.Channel : null; - public Stream OutputStream => _client.Server == Server ? _client.OutputStream : null; + public VoiceChannel Channel => _client.Server == Server ? _client.Channel : null; public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None; public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; @@ -31,7 +30,7 @@ namespace Discord.Audio } public Task Disconnect() => _client.Service.Leave(Server); - public Task Join(Channel channel) => _client.Join(channel); + public Task Join(VoiceChannel channel) => _client.Join(channel); public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); public void Clear() => _client.Clear(); diff --git a/src/Discord.Net.Audio/project.json b/src/Discord.Net.Audio/project.json index 664767ba7..c41a619c7 100644 --- a/src/Discord.Net.Audio/project.json +++ b/src/Discord.Net.Audio/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding voice support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -18,7 +18,7 @@ }, "dependencies": { - "Discord.Net": "0.9.0-rc3-3" + "Discord.Net": "1.0.0-alpha1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj deleted file mode 100644 index e3ce3c79a..000000000 --- a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj +++ /dev/null @@ -1,148 +0,0 @@ - - - - - Debug - AnyCPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740} - Library - Properties - Discord.Commands - Discord.Net.Commands - 512 - v4.5 - False - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET45 - prompt - 4 - 6 - true - - - pdbonly - true - bin\Release\ - TRACE;NET45 - prompt - 4 - true - 6 - - - - - - - Command.cs - - - CommandBuilder.cs - - - CommandErrorEventArgs.cs - - - CommandEventArgs.cs - - - CommandExtensions.cs - - - CommandMap.cs - - - CommandParameter.cs - - - CommandParser.cs - - - CommandService.cs - - - CommandServiceConfig.cs - - - HelpMode.cs - - - Permissions\GenericPermissionChecker.cs - - - Permissions\IPermissionChecker.cs - - - Permissions\Levels\PermissionLevelChecker.cs - - - Permissions\Levels\PermissionLevelExtensions.cs - - - Permissions\Levels\PermissionLevelService.cs - - - Permissions\Users\BlacklistChecker.cs - - - Permissions\Users\BlacklistExtensions.cs - - - Permissions\Users\BlacklistService.cs - - - Permissions\Users\UserlistService.cs - - - Permissions\Users\WhitelistChecker.cs - - - Permissions\Users\WhitelistExtensions.cs - - - Permissions\Users\WhitelistService.cs - - - Permissions\Visibility\PrivateChecker.cs - - - Permissions\Visibility\PrivateExtensions.cs - - - Permissions\Visibility\PublicChecker.cs - - - Permissions\Visibility\PublicExtensions.cs - - - - - - {8d71a857-879a-4a10-859e-5ff824ed6688} - Discord.Net - - - - - - - - project.json - - - - - - \ No newline at end of file diff --git a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs deleted file mode 100644 index 1959b2cf3..000000000 --- a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Discord.Net.Commands")] -[assembly: AssemblyDescription("A Discord.Net extension adding basic command support.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("RogueException")] -[assembly: AssemblyProduct("Discord.Net.Commands")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] -[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] - -[assembly: AssemblyVersion("0.9.0.0")] -[assembly: AssemblyFileVersion("0.9.0.0")] - diff --git a/src/Discord.Net.Commands.Net45/project.json b/src/Discord.Net.Commands.Net45/project.json deleted file mode 100644 index 62e5e6154..000000000 --- a/src/Discord.Net.Commands.Net45/project.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "frameworks": { - "net45": { } - }, - "runtimes": { - "win": { }, - "win-x86": { }, - "win-x64": { } - } -} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index a8addc1b1..ccd62798b 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -52,7 +52,7 @@ namespace Discord.Commands _checks = checks; } - internal bool CanRun(User user, Channel channel, out string error) + internal bool CanRun(User user, ITextChannel channel, out string error) { for (int i = 0; i < _checks.Length; i++) { diff --git a/src/Discord.Net.Commands/CommandBuilder.cs b/src/Discord.Net.Commands/CommandBuilder.cs index 129dc24ab..6b945841c 100644 --- a/src/Discord.Net.Commands/CommandBuilder.cs +++ b/src/Discord.Net.Commands/CommandBuilder.cs @@ -79,7 +79,7 @@ namespace Discord.Commands _checks.Add(check); return this; } - public CommandBuilder AddCheck(Func checkFunc, string errorMsg = null) + public CommandBuilder AddCheck(Func checkFunc, string errorMsg = null) { _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); return this; @@ -145,7 +145,7 @@ namespace Discord.Commands { _checks.Add(checker); } - public void AddCheck(Func checkFunc, string errorMsg = null) + public void AddCheck(Func checkFunc, string errorMsg = null) { _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); } diff --git a/src/Discord.Net.Commands/CommandEventArgs.cs b/src/Discord.Net.Commands/CommandEventArgs.cs index 818f5fa23..70793f5e1 100644 --- a/src/Discord.Net.Commands/CommandEventArgs.cs +++ b/src/Discord.Net.Commands/CommandEventArgs.cs @@ -10,8 +10,7 @@ namespace Discord.Commands public Command Command { get; } public User User => Message.User; - public Channel Channel => Message.Channel; - public Server Server => Message.Channel.Server; + public ITextChannel Channel => Message.Channel; public CommandEventArgs(Message message, Command command, string[] args) { diff --git a/src/Discord.Net.Commands/CommandExtensions.cs b/src/Discord.Net.Commands/CommandExtensions.cs index 557f5ac5a..c57cf099f 100644 --- a/src/Discord.Net.Commands/CommandExtensions.cs +++ b/src/Discord.Net.Commands/CommandExtensions.cs @@ -6,14 +6,14 @@ namespace Discord.Commands { public static DiscordClient UsingCommands(this DiscordClient client, CommandServiceConfig config = null) { - client.Services.Add(new CommandService(config)); + client.AddService(new CommandService(config)); return client; } public static DiscordClient UsingCommands(this DiscordClient client, Action configFunc = null) { var builder = new CommandServiceConfigBuilder(); configFunc(builder); - client.Services.Add(new CommandService(builder)); + client.AddService(new CommandService(builder)); return client; } } diff --git a/src/Discord.Net.Commands/CommandMap.cs b/src/Discord.Net.Commands/CommandMap.cs index 98decd833..ad280b335 100644 --- a/src/Discord.Net.Commands/CommandMap.cs +++ b/src/Discord.Net.Commands/CommandMap.cs @@ -116,7 +116,7 @@ namespace Discord.Commands } } - public bool CanRun(User user, Channel channel, out string error) + public bool CanRun(User user, ITextChannel channel, out string error) { error = null; if (_commands.Count > 0) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 5969b9691..ea530d27b 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -63,7 +63,7 @@ namespace Discord.Commands .Description("Returns information about commands.") .Do(async e => { - Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); + ITextChannel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); if (e.Args.Length > 0) //Show command help { var map = _map.GetItem(string.Join(" ", e.Args)); @@ -97,10 +97,15 @@ namespace Discord.Commands //Check for mention if (cmdMsg == null && Config.AllowMentionPrefix) { - if (msg.StartsWith(client.CurrentUser.Mention)) - cmdMsg = msg.Substring(client.CurrentUser.Mention.Length + 1); - else if (msg.StartsWith($"@{client.CurrentUser.Name}")) - cmdMsg = msg.Substring(client.CurrentUser.Name.Length + 1); + string mention = client.CurrentUser.Mention; + if (msg.StartsWith(mention) && msg.Length > mention.Length) + cmdMsg = msg.Substring(mention.Length + 1); + else + { + mention = $"@{client.CurrentUser.Name}"; + if (msg.StartsWith(mention) && msg.Length > mention.Length) + cmdMsg = msg.Substring(mention.Length + 1); + } } //Check using custom activator @@ -170,7 +175,7 @@ namespace Discord.Commands }; } - public Task ShowGeneralHelp(User user, Channel channel, Channel replyChannel = null) + public Task ShowGeneralHelp(User user, ITextChannel channel, ITextChannel replyChannel = null) { StringBuilder output = new StringBuilder(); bool isFirstCategory = true; @@ -214,32 +219,12 @@ namespace Discord.Commands if (output.Length == 0) output.Append("There are no commands you have permission to run."); else - { - output.Append("\n\n"); - - //TODO: Should prefix be stated in the help message or not? - /*StringBuilder builder = new StringBuilder(); - if (Config.PrefixChar != null) - { - builder.Append('`'); - builder.Append(Config.PrefixChar.Value); - builder.Append('`'); - } - if (Config.AllowMentionPrefix) - { - if (builder.Length > 0) - builder.Append(" or "); - builder.Append(Client.CurrentUser.Mention); - } - if (builder.Length > 0) - output.AppendLine($"Start your message with {builder.ToString()} to run a command.");*/ - output.AppendLine($"Run `help ` for more information."); - } + output.AppendLine("\n\nRun `help ` for more information."); return (replyChannel ?? channel).SendMessage(output.ToString()); } - private Task ShowCommandHelp(CommandMap map, User user, Channel channel, Channel replyChannel = null) + private Task ShowCommandHelp(CommandMap map, User user, ITextChannel channel, ITextChannel replyChannel = null) { StringBuilder output = new StringBuilder(); @@ -250,9 +235,7 @@ namespace Discord.Commands { foreach (var cmd in cmds) { - if (!cmd.CanRun(user, channel, out error)) { } - //output.AppendLine(error ?? DefaultPermissionError); - else + if (cmd.CanRun(user, channel, out error)) { if (isFirstCmd) isFirstCmd = false; @@ -294,7 +277,7 @@ namespace Discord.Commands return (replyChannel ?? channel).SendMessage(output.ToString()); } - public Task ShowCommandHelp(Command command, User user, Channel channel, Channel replyChannel = null) + public Task ShowCommandHelp(Command command, User user, ITextChannel channel, ITextChannel replyChannel = null) { StringBuilder output = new StringBuilder(); string error; @@ -304,7 +287,7 @@ namespace Discord.Commands ShowCommandHelpInternal(command, user, channel, output); return (replyChannel ?? channel).SendMessage(output.ToString()); } - private void ShowCommandHelpInternal(Command command, User user, Channel channel, StringBuilder output) + private void ShowCommandHelpInternal(Command command, User user, ITextChannel channel, StringBuilder output) { output.Append('`'); output.Append(command.Text); diff --git a/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs b/src/Discord.Net.Commands/GenericPermissionChecker.cs similarity index 54% rename from src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs rename to src/Discord.Net.Commands/GenericPermissionChecker.cs index 05e95ac64..10d665811 100644 --- a/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs +++ b/src/Discord.Net.Commands/GenericPermissionChecker.cs @@ -4,16 +4,16 @@ namespace Discord.Commands.Permissions { internal class GenericPermissionChecker : IPermissionChecker { - private readonly Func _checkFunc; + private readonly Func _checkFunc; private readonly string _error; - public GenericPermissionChecker(Func checkFunc, string error = null) + public GenericPermissionChecker(Func checkFunc, string error = null) { _checkFunc = checkFunc; _error = error; } - public bool CanRun(Command command, User user, Channel channel, out string error) + public bool CanRun(Command command, User user, ITextChannel channel, out string error) { error = _error; return _checkFunc(command, user, channel); diff --git a/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs b/src/Discord.Net.Commands/IPermissionChecker.cs similarity index 54% rename from src/Discord.Net.Commands/Permissions/IPermissionChecker.cs rename to src/Discord.Net.Commands/IPermissionChecker.cs index f400c3420..0f317ffef 100644 --- a/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs +++ b/src/Discord.Net.Commands/IPermissionChecker.cs @@ -2,6 +2,6 @@ { public interface IPermissionChecker { - bool CanRun(Command command, User user, Channel channel, out string error); + bool CanRun(Command command, User user, ITextChannel channel, out string error); } } diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs deleted file mode 100644 index 0ebe5c890..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Discord.Commands.Permissions.Levels -{ - public class PermissionLevelChecker : IPermissionChecker - { - private readonly PermissionLevelService _service; - private readonly int _minPermissions; - - public PermissionLevelService Service => _service; - public int MinPermissions => _minPermissions; - - internal PermissionLevelChecker(DiscordClient client, int minPermissions) - { - _service = client.Services.Get(true); - _minPermissions = minPermissions; - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = null; //Use default error text. - int permissions = _service.GetPermissionLevel(user, channel); - return permissions >= _minPermissions; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs deleted file mode 100644 index 79cae8857..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Discord.Commands.Permissions.Levels -{ - public static class PermissionLevelExtensions - { - public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func permissionResolver) - { - client.Services.Add(new PermissionLevelService(permissionResolver)); - return client; - } - - public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) - { - builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); - return builder; - } - public static CommandGroupBuilder MinPermissions(this CommandGroupBuilder builder, int minPermissions) - { - builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); - return builder; - } - public static CommandService MinPermissions(this CommandService service, int minPermissions) - { - service.Root.AddCheck(new PermissionLevelChecker(service.Client, minPermissions)); - return service; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs deleted file mode 100644 index 6bab13b97..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Discord.Commands.Permissions.Levels -{ - public class PermissionLevelService : IService - { - private readonly Func _getPermissionsFunc; - - private DiscordClient _client; - public DiscordClient Client => _client; - - public PermissionLevelService(Func getPermissionsFunc) - { - _getPermissionsFunc = getPermissionsFunc; - } - - public void Install(DiscordClient client) - { - _client = client; - } - public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs deleted file mode 100644 index 0c0b5500b..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class BlacklistChecker : IPermissionChecker - { - private readonly BlacklistService _service; - - internal BlacklistChecker(DiscordClient client) - { - _service = client.Services.Get(true); - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = null; //Use default error text. - return _service.CanRun(user); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs deleted file mode 100644 index 21de4076d..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; - -namespace Discord.Commands.Permissions.Userlist -{ - public static class BlacklistExtensions - { - public static DiscordClient UsingGlobalBlacklist(this DiscordClient client, params ulong[] initialUserIds) - { - client.Services.Add(new BlacklistService(initialUserIds)); - return client; - } - - public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) - { - builder.AddCheck(new BlacklistChecker(builder.Service.Client)); - return builder; - } - public static CommandGroupBuilder UseGlobalBlacklist(this CommandGroupBuilder builder) - { - builder.AddCheck(new BlacklistChecker(builder.Service.Client)); - return builder; - } - public static CommandService UseGlobalBlacklist(this CommandService service) - { - service.Root.AddCheck(new BlacklistChecker(service.Client)); - return service; - } - - public static IEnumerable GetBlacklistedUserIds(this DiscordClient client) - => client.Services.Get().UserIds; - public static void BlacklistUser(this DiscordClient client, User user) - { - client.Services.Get().Add(user.Id); - } - public static void BlacklistUser(this DiscordClient client, ulong userId) - { - client.Services.Get().Add(userId); - } - public static void UnBlacklistUser(this DiscordClient client, User user) - { - client.Services.Get().Remove(user.Id); - } - public static void UnBlacklistUser(this DiscordClient client, ulong userId) - { - client.Services.Get().Remove(userId); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs deleted file mode 100644 index ced4c3fdc..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class BlacklistService : UserlistService - { - public BlacklistService(params ulong[] initialList) - : base(initialList) - { - } - - public bool CanRun(User user) - => !_userList.ContainsKey(user.Id); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs deleted file mode 100644 index da8264312..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Discord.Commands.Permissions.Userlist -{ - public class UserlistService : IService - { - protected readonly ConcurrentDictionary _userList; - private DiscordClient _client; - - public DiscordClient Client => _client; - public IEnumerable UserIds => _userList.Select(x => x.Key); - - public UserlistService(params ulong[] initialUserIds) - { - _userList = new ConcurrentDictionary(); - for (int i = 0; i < initialUserIds.Length; i++) - _userList.TryAdd(initialUserIds[i], true); - } - - public void Add(User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - _userList[user.Id] = true; - } - public void Add(ulong userId) - { - _userList[userId] = true; - } - - public bool Remove(User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - bool ignored; - return _userList.TryRemove(user.Id, out ignored); - } - public bool Remove(ulong userId) - { - bool ignored; - return _userList.TryRemove(userId, out ignored); - } - - void IService.Install(DiscordClient client) - { - _client = client; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs deleted file mode 100644 index 783455e3a..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class WhitelistChecker : IPermissionChecker - { - private readonly WhitelistService _service; - - internal WhitelistChecker(DiscordClient client) - { - _service = client.Services.Get(true); - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = null; //Use default error text. - return _service.CanRun(user); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs deleted file mode 100644 index eaa136075..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; - -namespace Discord.Commands.Permissions.Userlist -{ - public static class WhitelistExtensions - { - public static DiscordClient UsingGlobalWhitelist(this DiscordClient client, params ulong[] initialUserIds) - { - client.Services.Add(new WhitelistService(initialUserIds)); - return client; - } - - public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) - { - builder.AddCheck(new WhitelistChecker(builder.Service.Client)); - return builder; - } - public static CommandGroupBuilder UseGlobalWhitelist(this CommandGroupBuilder builder) - { - builder.AddCheck(new WhitelistChecker(builder.Service.Client)); - return builder; - } - public static CommandService UseGlobalWhitelist(this CommandService service) - { - service.Root.AddCheck(new BlacklistChecker(service.Client)); - return service; - } - - public static IEnumerable GetWhitelistedUserIds(this DiscordClient client) - => client.Services.Get().UserIds; - public static void WhitelistUser(this DiscordClient client, User user) - { - client.Services.Get().Add(user.Id); - } - public static void WhitelistUser(this DiscordClient client, ulong userId) - { - client.Services.Get().Add(userId); - } - public static void UnWhitelistUser(this DiscordClient client, User user) - { - client.Services.Get().Remove(user.Id); - } - public static void RemoveFromWhitelist(this DiscordClient client, ulong userId) - { - client.Services.Get().Remove(userId); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs deleted file mode 100644 index ae25d3fd1..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class WhitelistService : UserlistService - { - public WhitelistService(params ulong[] initialList) - : base(initialList) - { - } - - public bool CanRun(User user) - => _userList.ContainsKey(user.Id); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs b/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs deleted file mode 100644 index dd336042d..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord.Commands.Permissions.Visibility -{ - public class PrivateChecker : IPermissionChecker - { - internal PrivateChecker() { } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - if (user.Server != null) - { - error = "This command may only be run in a private chat."; - return false; - } - else - { - error = null; - return true; - } - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs b/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs deleted file mode 100644 index cb3579983..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord.Commands.Permissions.Visibility -{ - public static class PrivateExtensions - { - public static CommandBuilder PrivateOnly(this CommandBuilder builder) - { - builder.AddCheck(new PrivateChecker()); - return builder; - } - public static CommandGroupBuilder PrivateOnly(this CommandGroupBuilder builder) - { - builder.AddCheck(new PrivateChecker()); - return builder; - } - public static CommandService PrivateOnly(this CommandService service) - { - service.Root.AddCheck(new PrivateChecker()); - return service; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs b/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs deleted file mode 100644 index 9e70b647b..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord.Commands.Permissions.Visibility -{ - public class PublicChecker : IPermissionChecker - { - internal PublicChecker() { } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - if (user.Server == null) - { - error = "This command can't be run in a private chat."; - return false; - } - else - { - error = null; - return true; - } - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs b/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs deleted file mode 100644 index 8cd78a4fe..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord.Commands.Permissions.Visibility -{ - public static class PublicExtensions - { - public static CommandBuilder PublicOnly(this CommandBuilder builder) - { - builder.AddCheck(new PublicChecker()); - return builder; - } - public static CommandGroupBuilder PublicOnly(this CommandGroupBuilder builder) - { - builder.AddCheck(new PublicChecker()); - return builder; - } - public static CommandService PublicOnly(this CommandService service) - { - service.Root.AddCheck(new PublicChecker()); - return service; - } - } -} diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index f93bb3d77..124a29dfe 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding basic command support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -16,7 +16,7 @@ }, "dependencies": { - "Discord.Net": "0.9.0-rc3-3" + "Discord.Net": "1.0.0-alpha1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj b/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj deleted file mode 100644 index cab137c25..000000000 --- a/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Debug - AnyCPU - {3091164F-66AE-4543-A63D-167C1116241D} - Library - Properties - Discord.Modules - Discord.Net.Modules - 512 - v4.5 - False - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET45 - prompt - 4 - 6 - true - true - - - pdbonly - true - bin\Release\ - TRACE;NET45 - prompt - 4 - true - 6 - true - - - - - - - IModule.cs - - - ModuleChecker.cs - - - ModuleExtensions.cs - - - ModuleFilter.cs - - - ModuleManager.cs - - - ModuleService.cs - - - - - - {1b5603b4-6f8f-4289-b945-7baae523d740} - Discord.Net.Commands - - - {8d71a857-879a-4a10-859e-5ff824ed6688} - Discord.Net - - - - - - project.json - - - - - - \ No newline at end of file diff --git a/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs deleted file mode 100644 index 04eda1587..000000000 --- a/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Discord.Net.Modules")] -[assembly: AssemblyDescription("A Discord.Net extension adding basic plugin support.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("RogueException")] -[assembly: AssemblyProduct("Discord.Net.Modules")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] -[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] - -[assembly: AssemblyVersion("0.9.0.0")] -[assembly: AssemblyFileVersion("0.9.0.0")] - diff --git a/src/Discord.Net.Modules.Net45/project.json b/src/Discord.Net.Modules.Net45/project.json deleted file mode 100644 index a174430f0..000000000 --- a/src/Discord.Net.Modules.Net45/project.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "dependencies": { - "Nito.AsyncEx": "3.0.1" - }, - "frameworks": { - "net45": { } - }, - "runtimes": { - "win": { }, - "win-x86": { }, - "win-x64": { } - } -} \ No newline at end of file diff --git a/src/Discord.Net.Modules/ModuleChecker.cs b/src/Discord.Net.Modules/ModuleChecker.cs index 7b54c8a2d..5f9b8e116 100644 --- a/src/Discord.Net.Modules/ModuleChecker.cs +++ b/src/Discord.Net.Modules/ModuleChecker.cs @@ -14,9 +14,11 @@ namespace Discord.Modules _filterType = manager.FilterType; } - public bool CanRun(Command command, User user, Channel channel, out string error) + public bool CanRun(Command command, User user, ITextChannel channel, out string error) { - if (_filterType == ModuleFilter.None || _filterType == ModuleFilter.AlwaysAllowPrivate || _manager.HasChannel(channel)) + if (_filterType == ModuleFilter.None || + _filterType == ModuleFilter.AlwaysAllowPrivate || + (channel.IsPublic && _manager.HasChannel(channel))) { error = null; return true; diff --git a/src/Discord.Net.Modules/ModuleExtensions.cs b/src/Discord.Net.Modules/ModuleExtensions.cs index 070ac9084..a96517c06 100644 --- a/src/Discord.Net.Modules/ModuleExtensions.cs +++ b/src/Discord.Net.Modules/ModuleExtensions.cs @@ -4,24 +4,26 @@ { public static DiscordClient UsingModules(this DiscordClient client) { - client.Services.Add(new ModuleService()); + client.AddService(new ModuleService()); return client; } - public static DiscordClient AddModule(this DiscordClient client, T instance, string name = null, ModuleFilter filter = ModuleFilter.None) - where T : class, IModule + public static void AddModule(this DiscordClient client, IModule instance, string name = null, ModuleFilter filter = ModuleFilter.None) { - client.Modules().Add(instance, name ?? nameof(T), filter); - return client; + client.GetService().Add(instance, name, filter); } - public static DiscordClient AddModule(this DiscordClient client, string name = null, ModuleFilter filter = ModuleFilter.None) + public static void AddModule(this DiscordClient client, string name = null, ModuleFilter filter = ModuleFilter.None) where T : class, IModule, new() { - client.Modules().Add(new T(), name ?? nameof(T), filter); - return client; + client.GetService().Add(name, filter); } - - public static ModuleService Modules(this DiscordClient client, bool required = true) - => client.Services.Get(required); + public static void AddModule(this DiscordClient client, T instance, string name = null, ModuleFilter filter = ModuleFilter.None) + where T : class, IModule + { + client.GetService().Add(instance, name, filter); + } + public static ModuleManager GetModule(this DiscordClient client) + where T : class, IModule + => client.GetService().Get(); } } diff --git a/src/Discord.Net.Modules/ModuleManager.cs b/src/Discord.Net.Modules/ModuleManager.cs index c2a8d400f..71b5b0c08 100644 --- a/src/Discord.Net.Modules/ModuleManager.cs +++ b/src/Discord.Net.Modules/ModuleManager.cs @@ -7,13 +7,19 @@ using System.Linq; namespace Discord.Modules { + public class ModuleManager : ModuleManager + where T : class, IModule + { + public new T Instance => base.Instance as T; + + internal ModuleManager(DiscordClient client, T instance, string name, ModuleFilter filterType) + : base(client, instance, name, filterType) + { + } + } + public class ModuleManager { - public event EventHandler ServerEnabled = delegate { }; - public event EventHandler ServerDisabled = delegate { }; - public event EventHandler ChannelEnabled = delegate { }; - public event EventHandler ChannelDisabled = delegate { }; - public event EventHandler JoinedServer = delegate { }; public event EventHandler LeftServer = delegate { }; public event EventHandler ServerUpdated = delegate { }; @@ -32,10 +38,8 @@ namespace Discord.Modules public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; public event EventHandler UserUpdated = delegate { }; - //public event EventHandler UserPresenceUpdated = delegate { }; - //public event EventHandler UserVoiceStateUpdated = delegate { }; public event EventHandler UserUnbanned = delegate { }; - public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler MessageReceived = delegate { }; public event EventHandler MessageSent = delegate { }; @@ -45,7 +49,7 @@ namespace Discord.Modules private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; private readonly ConcurrentDictionary _enabledServers; - private readonly ConcurrentDictionary _enabledChannels; + private readonly ConcurrentDictionary _enabledChannels; private readonly ConcurrentDictionary _indirectServers; private readonly AsyncLock _lock; @@ -56,7 +60,7 @@ namespace Discord.Modules public ModuleFilter FilterType { get; } public IEnumerable EnabledServers => _enabledServers.Select(x => x.Value); - public IEnumerable EnabledChannels => _enabledChannels.Select(x => x.Value); + public IEnumerable EnabledChannels => _enabledChannels.Select(x => x.Value); internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) { @@ -74,12 +78,17 @@ namespace Discord.Modules _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); _enabledServers = new ConcurrentDictionary(); - _enabledChannels = new ConcurrentDictionary(); + _enabledChannels = new ConcurrentDictionary(); _indirectServers = new ConcurrentDictionary(); if (_allowAll || _useServerWhitelist) //Server-only events { - client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; + client.ChannelCreated += (s, e) => + { + var server = (e.Channel as PublicChannel)?.Server; + if (HasServer(server)) + ChannelCreated(s, e); + }; //TODO: This *is* a channel update if the before/after voice channel is whitelisted //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; } @@ -105,17 +114,16 @@ namespace Discord.Modules client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; + //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; - //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist - //client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; } public void CreateCommands(string prefix, Action config) { - var commandService = Client.Services.Get(); + var commandService = Client.GetService(); commandService.CreateGroup(prefix, x => { x.Category(Name); @@ -144,16 +152,7 @@ namespace Discord.Modules EnableServerInternal(server); } } - private bool EnableServerInternal(Server server) - { - if (_enabledServers.TryAdd(server.Id, server)) - { - if (ServerEnabled != null) - ServerEnabled(this, new ServerEventArgs(server)); - return true; - } - return false; - } + private bool EnableServerInternal(Server server) => _enabledServers.TryAdd(server.Id, server); public bool DisableServer(Server server) { @@ -161,34 +160,18 @@ namespace Discord.Modules if (!_useServerWhitelist) return false; using (_lock.Lock()) - { - if (_enabledServers.TryRemove(server.Id, out server)) - { - if (ServerDisabled != null) - ServerDisabled(this, new ServerEventArgs(server)); - return true; - } - return false; - } + return _enabledServers.TryRemove(server.Id, out server); } public void DisableAllServers() { if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); if (!_useServerWhitelist) return; - using (_lock.Lock()) - { - if (ServerDisabled != null) - { - foreach (var server in _enabledServers) - ServerDisabled(this, new ServerEventArgs(server.Value)); - } - + using (_lock.Lock()) _enabledServers.Clear(); - } } - public bool EnableChannel(Channel channel) + public bool EnableChannel(ITextChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); @@ -196,7 +179,7 @@ namespace Discord.Modules using (_lock.Lock()) return EnableChannelInternal(channel); } - public void EnableChannels(IEnumerable channels) + public void EnableChannels(IEnumerable channels) { if (channels == null) throw new ArgumentNullException(nameof(channels)); if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); @@ -208,65 +191,55 @@ namespace Discord.Modules EnableChannelInternal(channel); } } - private bool EnableChannelInternal(Channel channel) + private bool EnableChannelInternal(ITextChannel channel) { if (_enabledChannels.TryAdd(channel.Id, channel)) - { - var server = channel.Server; - if (server != null) - { - int value = 0; - _indirectServers.TryGetValue(server.Id, out value); - value++; - _indirectServers[server.Id] = value; - } - if (ChannelEnabled != null) - ChannelEnabled(this, new ChannelEventArgs(channel)); + { + if (channel.Type != ChannelType.Private) + { + var server = (channel as PublicChannel)?.Server; + int value = 0; + _indirectServers.TryGetValue(server.Id, out value); + value++; + _indirectServers[server.Id] = value; + } return true; } return false; } - public bool DisableChannel(Channel channel) + public bool DisableChannel(IChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); if (!_useChannelWhitelist) return false; - using (_lock.Lock()) + IChannel ignored; + if (_enabledChannels.TryRemove(channel.Id, out ignored)) { - Channel ignored; - if (_enabledChannels.TryRemove(channel.Id, out ignored)) - { - var server = channel.Server; - if (server != null) - { - int value = 0; - _indirectServers.TryGetValue(server.Id, out value); - value--; - if (value <= 0) - _indirectServers.TryRemove(server.Id, out value); - else - _indirectServers[server.Id] = value; - } - if (ChannelDisabled != null) - ChannelDisabled(this, new ChannelEventArgs(channel)); - return true; - } - return false; - } - } + using (_lock.Lock()) + { + if (channel.Type != ChannelType.Private) + { + var server = (channel as PublicChannel)?.Server; + int value = 0; + _indirectServers.TryGetValue(server.Id, out value); + value--; + if (value <= 0) + _indirectServers.TryRemove(server.Id, out value); + else + _indirectServers[server.Id] = value; + } + return true; + } + } + return false; + } public void DisableAllChannels() { if (!_useChannelWhitelist) return; using (_lock.Lock()) { - if (ChannelDisabled != null) - { - foreach (var channel in _enabledChannels) - ChannelDisabled(this, new ChannelEventArgs(channel.Value)); - } - _enabledChannels.Clear(); _indirectServers.Clear(); } @@ -282,20 +255,20 @@ namespace Discord.Modules internal bool HasServer(Server server) => _allowAll || - _useServerWhitelist && _enabledServers.ContainsKey(server.Id); + (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)); internal bool HasIndirectServer(Server server) => _allowAll || (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); - internal bool HasChannel(Channel channel) + internal bool HasChannel(IChannel channel) { if (_allowAll) return true; - if (channel.IsPrivate) return _allowPrivate; + if (channel.Type == ChannelType.Private) return _allowPrivate; if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true; - if (_useServerWhitelist) + if (_useServerWhitelist && channel.IsPublic) { - var server = channel.Server; + var server = (channel as PublicChannel).Server; if (server == null) return false; if (_enabledServers.ContainsKey(server.Id)) return true; } diff --git a/src/Discord.Net.Modules/ModuleService.cs b/src/Discord.Net.Modules/ModuleService.cs index 29297f8d4..1f405a222 100644 --- a/src/Discord.Net.Modules/ModuleService.cs +++ b/src/Discord.Net.Modules/ModuleService.cs @@ -1,18 +1,23 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace Discord.Modules { public class ModuleService : IService { public DiscordClient Client { get; private set; } - - public IEnumerable Modules => _modules.Values; - private readonly Dictionary _modules; - public ModuleService() + private static readonly MethodInfo addMethod = typeof(ModuleService).GetTypeInfo().GetDeclaredMethods(nameof(Add)) + .Single(x => x.IsGenericMethodDefinition && x.GetParameters().Length == 3); + + public IEnumerable Modules => _modules.Values; + private readonly Dictionary _modules; + + public ModuleService() { - _modules = new Dictionary(); + _modules = new Dictionary(); } void IService.Install(DiscordClient client) @@ -20,29 +25,37 @@ namespace Discord.Modules Client = client; } - public T Add(T module, string name, ModuleFilter type) + public void Add(IModule instance, string name, ModuleFilter filter) + { + Type type = instance.GetType(); + addMethod.MakeGenericMethod(type).Invoke(this, new object[] { instance, name, filter }); + } + public void Add(string name, ModuleFilter filter) + where T : class, IModule, new() + => Add(new T(), name, filter); + public void Add(T instance, string name, ModuleFilter filter) where T : class, IModule { - if (module == null) throw new ArgumentNullException(nameof(module)); - if (name == null) throw new ArgumentNullException(nameof(name)); + if (instance == null) throw new ArgumentNullException(nameof(instance)); if (Client == null) throw new InvalidOperationException("Service needs to be added to a DiscordClient before modules can be installed."); - if (_modules.ContainsKey(module)) + + Type type = typeof(T); + if (name == null) name = type.Name; + if (_modules.ContainsKey(type)) throw new InvalidOperationException("This module has already been added."); - var manager = new ModuleManager(Client, module, name, type); - _modules.Add(module, manager); - module.Install(manager); - return module; + var manager = new ModuleManager(Client, instance, name, filter); + _modules.Add(type, manager); + instance.Install(manager); + } + public ModuleManager Get() + where T : class, IModule + { + ModuleManager manager; + if (_modules.TryGetValue(typeof(T), out manager)) + return manager as ModuleManager; + return null; } - - public ModuleManager GetManager(IModule module) - { - if (module == null) throw new ArgumentNullException(nameof(module)); - - ModuleManager result = null; - _modules.TryGetValue(module, out result); - return result; - } } } diff --git a/src/Discord.Net.Modules/project.json b/src/Discord.Net.Modules/project.json index f9834fcf1..e8e897cac 100644 --- a/src/Discord.Net.Modules/project.json +++ b/src/Discord.Net.Modules/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding basic plugin support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -16,8 +16,8 @@ }, "dependencies": { - "Discord.Net": "0.9.0-rc3-3", - "Discord.Net.Commands": "0.9.0-rc3" + "Discord.Net": "1.0.0-alpha1", + "Discord.Net.Commands": "1.0.0-alpha1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj deleted file mode 100644 index aec38d9d5..000000000 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ /dev/null @@ -1,621 +0,0 @@ - - - - - Debug - AnyCPU - {8D71A857-879A-4A10-859E-5FF824ED6688} - Library - Properties - Discord - Discord.Net - 512 - v4.5 - - - False - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET45 - prompt - 2 - true - 6 - true - - - pdbonly - true - bin\Release\ - TRACE;NET45 - prompt - 4 - true - true - 6 - - - true - bin\FullDebug\ - TRACE;DEBUG;NET45,TEST_RESPONSES - true - 2 - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - 6 - true - - - - - - - - API\Client\Common\Channel.cs - - - API\Client\Common\ChannelReference.cs - - - API\Client\Common\ExtendedGuild.cs - - - API\Client\Common\ExtendedMember.cs - - - API\Client\Common\Guild.cs - - - API\Client\Common\GuildReference.cs - - - API\Client\Common\Invite.cs - - - API\Client\Common\InviteReference.cs - - - API\Client\Common\Member.cs - - - API\Client\Common\MemberPresence.cs - - - API\Client\Common\MemberReference.cs - - - API\Client\Common\MemberVoiceState.cs - - - API\Client\Common\Message.cs - - - API\Client\Common\MessageReference.cs - - - API\Client\Common\Role.cs - - - API\Client\Common\RoleReference.cs - - - API\Client\Common\User.cs - - - API\Client\Common\UserReference.cs - - - API\Client\GatewaySocket\Commands\Heartbeat.cs - - - API\Client\GatewaySocket\Commands\Identify.cs - - - API\Client\GatewaySocket\Commands\RequestMembers.cs - - - API\Client\GatewaySocket\Commands\Resume.cs - - - API\Client\GatewaySocket\Commands\UpdateStatus.cs - - - API\Client\GatewaySocket\Commands\UpdateVoice.cs - - - API\Client\GatewaySocket\Events\ChannelCreate.cs - - - API\Client\GatewaySocket\Events\ChannelDelete.cs - - - API\Client\GatewaySocket\Events\ChannelUpdate.cs - - - API\Client\GatewaySocket\Events\GuildBanAdd.cs - - - API\Client\GatewaySocket\Events\GuildBanRemove.cs - - - API\Client\GatewaySocket\Events\GuildCreate.cs - - - API\Client\GatewaySocket\Events\GuildDelete.cs - - - API\Client\GatewaySocket\Events\GuildEmojisUpdate.cs - - - API\Client\GatewaySocket\Events\GuildIntegrationsUpdate.cs - - - API\Client\GatewaySocket\Events\GuildMemberAdd.cs - - - API\Client\GatewaySocket\Events\GuildMemberRemove.cs - - - API\Client\GatewaySocket\Events\GuildMembersChunk.cs - - - API\Client\GatewaySocket\Events\GuildMemberUpdate.cs - - - API\Client\GatewaySocket\Events\GuildRoleCreate.cs - - - API\Client\GatewaySocket\Events\GuildRoleDelete.cs - - - API\Client\GatewaySocket\Events\GuildRoleUpdate.cs - - - API\Client\GatewaySocket\Events\GuildUpdate.cs - - - API\Client\GatewaySocket\Events\MessageAck.cs - - - API\Client\GatewaySocket\Events\MessageCreate.cs - - - API\Client\GatewaySocket\Events\MessageDelete.cs - - - API\Client\GatewaySocket\Events\MessageUpdate.cs - - - API\Client\GatewaySocket\Events\PresenceUpdate.cs - - - API\Client\GatewaySocket\Events\Ready.cs - - - API\Client\GatewaySocket\Events\Redirect.cs - - - API\Client\GatewaySocket\Events\Resumed.cs - - - API\Client\GatewaySocket\Events\TypingStart.cs - - - API\Client\GatewaySocket\Events\UserSettingsUpdate.cs - - - API\Client\GatewaySocket\Events\UserUpdate.cs - - - API\Client\GatewaySocket\Events\VoiceServerUpdate.cs - - - API\Client\GatewaySocket\Events\VoiceStateUpdate.cs - - - API\Client\GatewaySocket\OpCodes.cs - - - API\Client\IWebSocketMessage.cs - - - API\Client\Rest\AcceptInvite.cs - - - API\Client\Rest\AckMessage.cs - - - API\Client\Rest\AddChannelPermission.cs - - - API\Client\Rest\AddGuildBan.cs - - - API\Client\Rest\CreateChannel.cs - - - API\Client\Rest\CreateGuild.cs - - - API\Client\Rest\CreateInvite.cs - - - API\Client\Rest\CreatePrivateChannel.cs - - - API\Client\Rest\CreateRole.cs - - - API\Client\Rest\DeleteChannel.cs - - - API\Client\Rest\DeleteInvite.cs - - - API\Client\Rest\DeleteMessage.cs - - - API\Client\Rest\DeleteRole.cs - - - API\Client\Rest\Gateway.cs - - - API\Client\Rest\GetBans.cs - - - API\Client\Rest\GetInvite.cs - - - API\Client\Rest\GetInvites.cs - - - API\Client\Rest\GetMessages.cs - - - API\Client\Rest\GetVoiceRegions.cs - - - API\Client\Rest\GetWidget.cs - - - API\Client\Rest\KickMember.cs - - - API\Client\Rest\LeaveGuild.cs - - - API\Client\Rest\Login.cs - - - API\Client\Rest\Logout.cs - - - API\Client\Rest\PruneMembers.cs - - - API\Client\Rest\RemoveChannelPermission.cs - - - API\Client\Rest\RemoveGuildBan.cs - - - API\Client\Rest\ReorderChannels.cs - - - API\Client\Rest\ReorderRoles.cs - - - API\Client\Rest\SendFile.cs - - - API\Client\Rest\SendIsTyping.cs - - - API\Client\Rest\SendMessage.cs - - - API\Client\Rest\UpdateChannel.cs - - - API\Client\Rest\UpdateGuild.cs - - - API\Client\Rest\UpdateMember.cs - - - API\Client\Rest\UpdateMessage.cs - - - API\Client\Rest\UpdateProfile.cs - - - API\Client\Rest\UpdateRole.cs - - - API\Client\VoiceSocket\Commands\Heartbeat.cs - - - API\Client\VoiceSocket\Commands\Identify.cs - - - API\Client\VoiceSocket\Commands\SelectProtocol.cs - - - API\Client\VoiceSocket\Commands\SetSpeaking.cs - - - API\Client\VoiceSocket\Events\Ready.cs - - - API\Client\VoiceSocket\Events\SessionDescription.cs - - - API\Client\VoiceSocket\Events\Speaking.cs - - - API\Client\VoiceSocket\OpCodes.cs - - - API\Converters.cs - - - API\Extensions.cs - - - API\IRestRequest.cs - - - API\Status\Common\StatusResult.cs - - - API\Status\Rest\ActiveMaintenances.cs - - - API\Status\Rest\AllIncidents.cs - - - API\Status\Rest\UnresolvedIncidents.cs - - - API\Status\Rest\UpcomingMaintenances.cs - - - DiscordClient.cs - - - DiscordClient.Events.cs - - - DiscordConfig.cs - - - DynamicIL.cs - - - Enums\ChannelType.cs - - - Enums\ConnectionState.cs - - - Enums\ImageType.cs - - - Enums\LogSeverity.cs - - - Enums\PermissionBits.cs - - - Enums\PermissionTarget.cs - - - Enums\PermValue.cs - - - Enums\Relative.cs - - - Enums\StringEnum.cs - - - Enums\UserStatus.cs - - - ETF\ETFReader.cs - - - ETF\ETFType.cs - - - ETF\ETFWriter.cs - - - Events\ChannelEventArgs.cs - - - Events\ChannelUpdatedEventArgs.cs - - - Events\ChannelUserEventArgs.cs - - - Events\DisconnectedEventArgs.cs - - - Events\LogMessageEventArgs.cs - - - Events\MessageEventArgs.cs - - - Events\MessageUpdatedEventArgs.cs - - - Events\ProfileUpdatedEventArgs.cs - - - Events\RoleEventArgs.cs - - - Events\RoleUpdatedEventArgs.cs - - - Events\ServerEventArgs.cs - - - Events\ServerUpdatedEventArgs.cs - - - Events\UserEventArgs.cs - - - Events\UserUpdatedEventArgs.cs - - - Extensions.cs - - - Format.cs - - - IMentionable.cs - - - IService.cs - - - Legacy.cs - - - Logging\ILogger.cs - - - Logging\Logger.cs - - - Logging\LogManager.cs - - - MessageQueue.cs - - - Models\Channel.cs - - - Models\Color.cs - - - Models\Invite.cs - - - Models\Message.cs - - - Models\Permissions.cs - - - Models\Profile.cs - - - Models\Region.cs - - - Models\Role.cs - - - Models\Server.cs - - - Models\User.cs - - - Net\HttpException.cs - - - Net\Rest\CompletedRequestEventArgs.cs - - - Net\Rest\ETFRestClient.cs - - - Net\Rest\IRestEngine.cs - - - Net\Rest\JsonRestClient.cs - - - Net\Rest\RequestEventArgs.cs - - - Net\Rest\RestClient.cs - - - Net\Rest\SharpRestEngine.cs - - - Net\TimeoutException.cs - - - Net\WebSockets\WebSocketException.cs - - - Net\WebSockets\BinaryMessageEventArgs.cs - - - Net\WebSockets\BuiltInEngine.cs - - - Net\WebSockets\GatewaySocket.cs - - - Net\WebSockets\IWebSocketEngine.cs - - - Net\WebSockets\TextMessageEventArgs.cs - - - Net\WebSockets\WebSocket.cs - - - Net\WebSockets\WebSocketEventEventArgs.cs - - - Net\WebSockets\WS4NetEngine.cs - - - ServiceManager.cs - - - TaskManager.cs - - - - - - project.json - - - - - - - - - - \ No newline at end of file diff --git a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs deleted file mode 100644 index 64a8616f5..000000000 --- a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Discord.Net")] -[assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("RogueException")] -[assembly: AssemblyProduct("Discord.Net")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] -[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] - -[assembly: AssemblyVersion("0.9.0.0")] -[assembly: AssemblyFileVersion("0.9.0.0")] diff --git a/src/Discord.Net.Net45/project.json b/src/Discord.Net.Net45/project.json deleted file mode 100644 index 82e8e459f..000000000 --- a/src/Discord.Net.Net45/project.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "dependencies": { - "Newtonsoft.Json": "8.0.1", - "Nito.AsyncEx": "3.0.1", - "RestSharp": "105.2.3", - "WebSocket4Net": "0.14.1" - }, - "frameworks": { - "net45": { } - }, - "runtimes": { - "win": { }, - "win-x86": { }, - "win-x64": { } - } -} \ No newline at end of file diff --git a/src/Discord.Net/API/Client/Common/Channel.cs b/src/Discord.Net/API/Client/Common/Channel.cs index 90ed8bf38..37eac8e48 100644 --- a/src/Discord.Net/API/Client/Common/Channel.cs +++ b/src/Discord.Net/API/Client/Common/Channel.cs @@ -29,5 +29,7 @@ namespace Discord.API.Client public PermissionOverwrite[] PermissionOverwrites { get; set; } [JsonProperty("recipient")] public UserReference Recipient { get; set; } + [JsonProperty("bitrate")] + public int Bitrate { get; set; } } } diff --git a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs index 865e37c2d..2940c98ac 100644 --- a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs +++ b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"invite/{InviteId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public string InviteId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/AckMessage.cs b/src/Discord.Net/API/Client/Rest/AckMessage.cs index 1678ed34b..4cf238b72 100644 --- a/src/Discord.Net/API/Client/Rest/AckMessage.cs +++ b/src/Discord.Net/API/Client/Rest/AckMessage.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } public ulong MessageId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs index c7a9f57ac..bf725bcaf 100644 --- a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs +++ b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs @@ -4,12 +4,11 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public class AddChannelPermissionsRequest : IRestRequest + public class AddOrUpdateChannelPermissionsRequest : IRestRequest { string IRestRequest.Method => "PUT"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } @@ -22,7 +21,7 @@ namespace Discord.API.Client.Rest [JsonProperty("deny")] public uint Deny { get; set; } - public AddChannelPermissionsRequest(ulong channelId) + public AddOrUpdateChannelPermissionsRequest(ulong channelId) { ChannelId = channelId; } diff --git a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs index 7699a74e4..3e0b165f5 100644 --- a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs +++ b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PUT"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/CreateChannel.cs b/src/Discord.Net/API/Client/Rest/CreateChannel.cs index 0dc45bc43..90d9afec0 100644 --- a/src/Discord.Net/API/Client/Rest/CreateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/CreateChannel.cs @@ -8,14 +8,13 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("type")] - public string Type { get; set; } + public ChannelType Type { get; set; } public CreateChannelRequest(ulong guildId) { diff --git a/src/Discord.Net/API/Client/Rest/CreateGuild.cs b/src/Discord.Net/API/Client/Rest/CreateGuild.cs index baa1f455e..a18d2bee9 100644 --- a/src/Discord.Net/API/Client/Rest/CreateGuild.cs +++ b/src/Discord.Net/API/Client/Rest/CreateGuild.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; [JsonProperty("name")] public string Name { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/CreateInvite.cs b/src/Discord.Net/API/Client/Rest/CreateInvite.cs index a55b9c7e9..73f15c248 100644 --- a/src/Discord.Net/API/Client/Rest/CreateInvite.cs +++ b/src/Discord.Net/API/Client/Rest/CreateInvite.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs index 2d413a8d9..e1087dc36 100644 --- a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs @@ -9,7 +9,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"users/@me/channels"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; [JsonProperty("recipient_id"), JsonConverter(typeof(LongStringConverter))] public ulong RecipientId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/CreateRole.cs b/src/Discord.Net/API/Client/Rest/CreateRole.cs index 87715490d..3978c6aaa 100644 --- a/src/Discord.Net/API/Client/Rest/CreateRole.cs +++ b/src/Discord.Net/API/Client/Rest/CreateRole.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs index 6443c2387..ae56934b5 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/DeleteGuild.cs b/src/Discord.Net/API/Client/Rest/DeleteGuild.cs new file mode 100644 index 000000000..44df5892e --- /dev/null +++ b/src/Discord.Net/API/Client/Rest/DeleteGuild.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteGuildRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"guilds/{GuildId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public DeleteGuildRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs index 5de8b348b..4bfe1e0d7 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"invite/{InviteCode}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public string InviteCode { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs index 33921cd1a..3f781a756 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } public ulong MessageId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/DeleteRole.cs b/src/Discord.Net/API/Client/Rest/DeleteRole.cs index 650ece9f2..56faf3d33 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteRole.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteRole.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong RoleId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/Gateway.cs b/src/Discord.Net/API/Client/Rest/Gateway.cs index ef9486ca1..02dd71008 100644 --- a/src/Discord.Net/API/Client/Rest/Gateway.cs +++ b/src/Discord.Net/API/Client/Rest/Gateway.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"gateway"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } public class GatewayResponse diff --git a/src/Discord.Net/API/Client/Rest/GetBans.cs b/src/Discord.Net/API/Client/Rest/GetBans.cs index ee07cb242..714cdbaf8 100644 --- a/src/Discord.Net/API/Client/Rest/GetBans.cs +++ b/src/Discord.Net/API/Client/Rest/GetBans.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetInvite.cs b/src/Discord.Net/API/Client/Rest/GetInvite.cs index 27de264f0..2531ac26a 100644 --- a/src/Discord.Net/API/Client/Rest/GetInvite.cs +++ b/src/Discord.Net/API/Client/Rest/GetInvite.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"invite/{InviteCode}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public string InviteCode { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetInvites.cs b/src/Discord.Net/API/Client/Rest/GetInvites.cs index 079c54ef5..2b4f2f5fe 100644 --- a/src/Discord.Net/API/Client/Rest/GetInvites.cs +++ b/src/Discord.Net/API/Client/Rest/GetInvites.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetMessages.cs b/src/Discord.Net/API/Client/Rest/GetMessages.cs index b72b05c8b..1beadb9a9 100644 --- a/src/Discord.Net/API/Client/Rest/GetMessages.cs +++ b/src/Discord.Net/API/Client/Rest/GetMessages.cs @@ -19,7 +19,6 @@ namespace Discord.API.Client.Rest } } object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs index 7dc97ef31..df21cc203 100644 --- a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs +++ b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"voice/regions"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } public class GetVoiceRegionsResponse diff --git a/src/Discord.Net/API/Client/Rest/GetWidget.cs b/src/Discord.Net/API/Client/Rest/GetWidget.cs index 3b1006358..0437a8b6b 100644 --- a/src/Discord.Net/API/Client/Rest/GetWidget.cs +++ b/src/Discord.Net/API/Client/Rest/GetWidget.cs @@ -9,7 +9,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/KickMember.cs b/src/Discord.Net/API/Client/Rest/KickMember.cs index 96804ff6b..4808f8543 100644 --- a/src/Discord.Net/API/Client/Rest/KickMember.cs +++ b/src/Discord.Net/API/Client/Rest/KickMember.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs index 6a8b3c0cf..99fd8cbe7 100644 --- a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs +++ b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs @@ -6,9 +6,8 @@ namespace Discord.API.Client.Rest public class LeaveGuildRequest : IRestRequest { string IRestRequest.Method => "DELETE"; - string IRestRequest.Endpoint => $"guilds/{GuildId}"; + string IRestRequest.Endpoint => $"users/@me/guilds/{GuildId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/Login.cs b/src/Discord.Net/API/Client/Rest/Login.cs index ab7efc31b..f9c89c717 100644 --- a/src/Discord.Net/API/Client/Rest/Login.cs +++ b/src/Discord.Net/API/Client/Rest/Login.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => Email != null ? "POST" : "GET"; string IRestRequest.Endpoint => $"auth/login"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] public string Email { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/Logout.cs b/src/Discord.Net/API/Client/Rest/Logout.cs index 78f8059e5..9f4443c51 100644 --- a/src/Discord.Net/API/Client/Rest/Logout.cs +++ b/src/Discord.Net/API/Client/Rest/Logout.cs @@ -8,6 +8,5 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"auth/logout"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } } diff --git a/src/Discord.Net/API/Client/Rest/PruneMembers.cs b/src/Discord.Net/API/Client/Rest/PruneMembers.cs index 41771f7d6..e80498bb1 100644 --- a/src/Discord.Net/API/Client/Rest/PruneMembers.cs +++ b/src/Discord.Net/API/Client/Rest/PruneMembers.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => IsSimulation ? "GET" : "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/prune?days={Days}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs index c704eadbc..b453cba49 100644 --- a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs +++ b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } public ulong TargetId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs index c6d48c944..5a8f4f796 100644 --- a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs +++ b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs index c481eda43..c13f8b21c 100644 --- a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs +++ b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs @@ -17,7 +17,6 @@ namespace Discord.API.Client.Rest return ChannelIds.Select(x => new Channel(x, pos++)); } } - bool IRestRequest.IsPrivate => false; public class Channel { diff --git a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs index 23d73541f..300176a76 100644 --- a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs +++ b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs @@ -17,7 +17,6 @@ namespace Discord.API.Client.Rest return RoleIds.Select(x => new Role(x, pos++)); } } - bool IRestRequest.IsPrivate => false; public class Role { diff --git a/src/Discord.Net/API/Client/Rest/SendFile.cs b/src/Discord.Net/API/Client/Rest/SendFile.cs index 8d072d0e3..4b59e1a11 100644 --- a/src/Discord.Net/API/Client/Rest/SendFile.cs +++ b/src/Discord.Net/API/Client/Rest/SendFile.cs @@ -9,7 +9,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; string IRestFileRequest.Filename => Filename; Stream IRestFileRequest.Stream => Stream; diff --git a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs index aab017c67..4c56da0be 100644 --- a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs +++ b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/typing"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/SendMessage.cs b/src/Discord.Net/API/Client/Rest/SendMessage.cs index 6c6d1ae10..9caca991d 100644 --- a/src/Discord.Net/API/Client/Rest/SendMessage.cs +++ b/src/Discord.Net/API/Client/Rest/SendMessage.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs index cccd4b096..8a82caefd 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"channels/{ChannelId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } @@ -18,6 +17,8 @@ namespace Discord.API.Client.Rest public string Topic { get; set; } [JsonProperty("position")] public int Position { get; set; } + [JsonProperty("bitrate")] + public int Bitrate { get; set; } public UpdateChannelRequest(ulong channelId) { diff --git a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs index 4ff530554..f36b18d9f 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs @@ -9,7 +9,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/UpdateMember.cs b/src/Discord.Net/API/Client/Rest/UpdateMember.cs index 019c4ee99..ce1649bdd 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateMember.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateMember.cs @@ -9,7 +9,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs index df3ca46c0..fc055b2bc 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong ChannelId { get; set; } public ulong MessageId { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs index 08f28d868..0f0cdb313 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"users/@me"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; [JsonProperty("password")] public string CurrentPassword { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/UpdateRole.cs b/src/Discord.Net/API/Client/Rest/UpdateRole.cs index 7aac774b7..4bea0b52b 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateRole.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateRole.cs @@ -8,7 +8,6 @@ namespace Discord.API.Client.Rest string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; object IRestRequest.Payload => this; - bool IRestRequest.IsPrivate => false; public ulong GuildId { get; set; } public ulong RoleId { get; set; } diff --git a/src/Discord.Net/API/IRestRequest.cs b/src/Discord.Net/API/IRestRequest.cs index b8c7b818c..af520370d 100644 --- a/src/Discord.Net/API/IRestRequest.cs +++ b/src/Discord.Net/API/IRestRequest.cs @@ -7,7 +7,6 @@ namespace Discord.API string Method { get; } string Endpoint { get; } object Payload { get; } - bool IsPrivate { get; } } public interface IRestRequest : IRestRequest where ResponseT : class diff --git a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs index 638c176a5..639f85f08 100644 --- a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs +++ b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs @@ -8,6 +8,5 @@ namespace Discord.API.Status.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"scheduled-maintenances/active.json"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } } diff --git a/src/Discord.Net/API/Status/Rest/AllIncidents.cs b/src/Discord.Net/API/Status/Rest/AllIncidents.cs index 81a82ce51..9575bbd43 100644 --- a/src/Discord.Net/API/Status/Rest/AllIncidents.cs +++ b/src/Discord.Net/API/Status/Rest/AllIncidents.cs @@ -8,6 +8,5 @@ namespace Discord.API.Status.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"incidents.json"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } } diff --git a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs index 1665dde75..3cff11c23 100644 --- a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs +++ b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs @@ -8,6 +8,5 @@ namespace Discord.API.Status.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"incidents/unresolved.json"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } } diff --git a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs index afc812cc9..803a8a630 100644 --- a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs +++ b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs @@ -8,6 +8,5 @@ namespace Discord.API.Status.Rest string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"scheduled-maintenances/upcoming.json"; object IRestRequest.Payload => null; - bool IRestRequest.IsPrivate => false; } } diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 5d2577627..b05725f9b 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -5,7 +5,7 @@ namespace Discord { public partial class DiscordClient { - public event EventHandler LoggedIn = delegate { }; + public event EventHandler Ready = delegate { }; //public event EventHandler LoggedOut = delegate { }; public event EventHandler ChannelCreated = delegate { }; public event EventHandler ChannelDestroyed = delegate { }; @@ -25,22 +25,22 @@ namespace Discord public event EventHandler ServerUpdated = delegate { }; public event EventHandler ServerUnavailable = delegate { }; public event EventHandler UserBanned = delegate { }; - public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; public event EventHandler UserUpdated = delegate { }; public event EventHandler UserUnbanned = delegate { }; - private void OnLoggedIn() - => OnEvent(LoggedIn); + private void OnReady() + => OnEvent(Ready); /*private void OnLoggedOut(bool wasUnexpected, Exception ex) => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ - private void OnChannelCreated(Channel channel) + private void OnChannelCreated(IChannel channel) => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); - private void OnChannelDestroyed(Channel channel) + private void OnChannelDestroyed(IChannel channel) => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); - private void OnChannelUpdated(Channel before, Channel after) + private void OnChannelUpdated(IChannel before, IChannel after) => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); private void OnMessageAcknowledged(Message msg) @@ -77,8 +77,8 @@ namespace Discord private void OnUserBanned(User user) => OnEvent(UserBanned, new UserEventArgs(user)); - private void OnUserIsTypingUpdated(Channel channel, User user) - => OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user)); + private void OnUserIsTypingUpdated(ITextChannel channel, User user) + => OnEvent(UserIsTyping, new TypingEventArgs(channel, user)); private void OnUserJoined(User user) => OnEvent(UserJoined, new UserEventArgs(user)); private void OnUserLeft(User user) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 1d3343f16..5fa94bbc2 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -1,4 +1,5 @@ -using Discord.API.Client.GatewaySocket; +using APIChannel = Discord.API.Client.Channel; +using Discord.API.Client.GatewaySocket; using Discord.API.Client.Rest; using Discord.Logging; using Discord.Net; @@ -27,10 +28,12 @@ namespace Discord private readonly ManualResetEvent _disconnectedEvent; private readonly ManualResetEventSlim _connectedEvent; private readonly TaskManager _taskManager; - private readonly ConcurrentDictionary _servers; - private readonly ConcurrentDictionary _channels; - private readonly ConcurrentDictionary _privateChannels; //Key = RecipientId + private readonly ServiceCollection _services; + private ConcurrentDictionary _servers; + private ConcurrentDictionary _channels; + private ConcurrentDictionary _privateChannels; //Key = RecipientId private Dictionary _regions; + private Stopwatch _connectionStopwatch; internal Logger Logger { get; } @@ -44,8 +47,6 @@ namespace Discord public RestClient StatusAPI { get; } /// Gets the internal WebSocket for the Gateway event stream. public GatewaySocket GatewaySocket { get; } - /// Gets the service manager used for adding extensions to this client. - public ServiceManager Services { get; } /// Gets the queue used for outgoing messages, if enabled. public MessageQueue MessageQueue { get; } /// Gets the JSON serializer used by this client. @@ -66,10 +67,12 @@ namespace Discord /// Gets the game the current user is displayed as playing. public string CurrentGame { get; private set; } + /// Gets a collection of all extensions added to this DiscordClient. + public IEnumerable Services => _services; /// Gets a collection of all servers this client is a member of. public IEnumerable Servers => _servers.Select(x => x.Value); /// Gets a collection of all private channels this client is a member of. - public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); + public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); /// Gets a collection of all voice regions currently offered by Discord. public IEnumerable Regions => _regions.Select(x => x.Value); @@ -108,6 +111,8 @@ namespace Discord //Logging Log = new LogManager(this); Logger = Log.CreateLogger("Discord"); + if (config.LogLevel >= LogSeverity.Verbose) + _connectionStopwatch = new Stopwatch(); //Async _taskManager = new TaskManager(Cleanup); @@ -117,9 +122,10 @@ namespace Discord CancelToken = new CancellationToken(true); //Cache - _servers = new ConcurrentDictionary(); - _channels = new ConcurrentDictionary(); - _privateChannels = new ConcurrentDictionary(); + //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) + _servers = new ConcurrentDictionary(2, 0); + _channels = new ConcurrentDictionary(2, 0); + _privateChannels = new ConcurrentDictionary(2, 0); //Serialization Serializer = new JsonSerializer(); @@ -141,18 +147,14 @@ namespace Discord ClientAPI = new JsonRestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); StatusAPI = new JsonRestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); - GatewaySocket.Connected += (s, e) => - { - if (State == ConnectionState.Connecting) - EndConnect(); - }; + //GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception); GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); //Extensibility - Services = new ServiceManager(this); + _services = new ServiceCollection(this); } /// Connects to the Discord server with the provided email and password. @@ -184,7 +186,10 @@ namespace Discord Stopwatch stopwatch = null; if (Config.LogLevel >= LogSeverity.Verbose) + { + _connectionStopwatch.Restart(); stopwatch = Stopwatch.StartNew(); + } State = ConnectionState.Connecting; _disconnectedEvent.Reset(); @@ -206,7 +211,7 @@ namespace Discord { stopwatch.Stop(); double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); - Logger.Verbose($"Connection took {seconds} sec"); + Logger.Verbose($"Handshake + Ready took {seconds} sec"); } } } @@ -236,7 +241,7 @@ namespace Discord } } - ClientAPI.Token = token; + ClientAPI.Token = token; var request = new LoginRequest() { Email = email, Password = password }; var response = await ClientAPI.Send(request).ConfigureAwait(false); token = response.Token; @@ -251,11 +256,21 @@ namespace Discord } private void EndConnect() { - State = ConnectionState.Connected; - _connectedEvent.Set(); + if (State == ConnectionState.Connecting) + { + State = ConnectionState.Connected; + _connectedEvent.Set(); - SendStatus(); - OnLoggedIn(); + if (Config.LogLevel >= LogSeverity.Verbose) + { + _connectionStopwatch.Stop(); + double seconds = Math.Round(_connectionStopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); + Logger.Verbose($"Connection took {seconds} sec"); + } + + SendStatus(); + OnReady(); + } } /// Disconnects from the Discord server, canceling any pending requests. @@ -321,45 +336,47 @@ namespace Discord } #region Channels - internal void AddChannel(Channel channel) + internal void AddChannel(IChannel channel) { _channels.GetOrAdd(channel.Id, channel); } - private Channel RemoveChannel(ulong id) + private IChannel RemoveChannel(ulong id) { - Channel channel; + IChannel channel; if (_channels.TryRemove(id, out channel)) { if (channel.IsPrivate) - _privateChannels.TryRemove(channel.Recipient.Id, out channel); + { + PrivateChannel removed; + _privateChannels.TryRemove((channel as PrivateChannel).Recipient.Id, out removed); + } else - channel.Server.RemoveChannel(id); + (channel as PublicChannel).Server.RemoveChannel(id); } return channel; } - public Channel GetChannel(ulong id) + public IChannel GetChannel(ulong id) { - Channel channel; + IChannel channel; _channels.TryGetValue(id, out channel); return channel; } - private Channel AddPrivateChannel(ulong id, ulong recipientId) + private PrivateChannel AddPrivateChannel(APIChannel model) { - Channel channel; - if (_channels.TryGetOrAdd(id, x => new Channel(this, x, new User(this, recipientId, null)), out channel)) - _privateChannels[recipientId] = channel; - return channel; + IChannel channel; + if (_channels.TryGetOrAdd(model.Id, x => new PrivateChannel(x, new User(model.Recipient, this, null), model), out channel)) + _privateChannels[model.Recipient.Id] = channel as PrivateChannel; + return channel as PrivateChannel; } - internal Channel GetPrivateChannel(ulong recipientId) + internal PrivateChannel GetPrivateChannel(ulong recipientId) { - Channel channel; + PrivateChannel channel; _privateChannels.TryGetValue(recipientId, out channel); return channel; } - internal Task CreatePMChannel(User user) - => CreatePrivateChannel(user.Id); - public async Task CreatePrivateChannel(ulong userId) + public Task CreatePrivateChannel(User user) => CreatePrivateChannel(user.Id); + public async Task CreatePrivateChannel(ulong userId) { var channel = GetPrivateChannel(userId); if (channel != null) return channel; @@ -367,9 +384,7 @@ namespace Discord var request = new CreatePrivateChannelRequest() { RecipientId = userId }; var response = await ClientAPI.Send(request).ConfigureAwait(false); - channel = AddPrivateChannel(response.Id, userId); - channel.Update(response); - return channel; + return AddPrivateChannel(response); } #endregion @@ -392,7 +407,7 @@ namespace Discord try { var response = await ClientAPI.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass); + var invite = new Invite(response, this); invite.Update(response); return invite; } @@ -415,8 +430,7 @@ namespace Discord #endregion #region Servers - private Server AddServer(ulong id) - => _servers.GetOrAdd(id, x => new Server(this, x)); + private Server AddServer(ulong id) => _servers.GetOrAdd(id, x => new Server(x, this)); private Server RemoveServer(ulong id) { Server server; @@ -434,11 +448,6 @@ namespace Discord _servers.TryGetValue(id, out server); return server; } - public IEnumerable FindServers(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return _servers.Select(x => x.Value).Find(name); - } /// Creates a new server with the provided name and region. public async Task CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) @@ -470,19 +479,29 @@ namespace Discord //Global case "READY": { - Stopwatch stopwatch = null; - if (Config.LogLevel >= LogSeverity.Verbose) - stopwatch = Stopwatch.StartNew(); + //TODO: None of this is really threadsafe - should only replace the cache collections when they have been fully populated + var data = e.Payload.ToObject(Serializer); + + int channelCount = 0; + for (int i = 0; i < data.Guilds.Length; i++) + channelCount += data.Guilds[i].Channels.Length; + + //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) + _servers = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 1.05)); + _channels = new ConcurrentDictionary(2, (int)(channelCount * 1.05)); + _privateChannels = new ConcurrentDictionary(2, (int)(data.PrivateChannels.Length * 1.05)); + List largeServers = new List(); + SessionId = data.SessionId; - PrivateUser = new User(this, data.User.Id, null); + PrivateUser = new User(data.User, this, null); PrivateUser.Update(data.User); - CurrentUser = new Profile(this, data.User.Id); + CurrentUser = new Profile(data.User, this); CurrentUser.Update(data.User); - List largeServers = new List(); - foreach (var model in data.Guilds) + for (int i = 0; i < data.Guilds.Length; i++) { + var model = data.Guilds[i]; if (model.Unavailable != true) { var server = AddServer(model.Id); @@ -491,19 +510,12 @@ namespace Discord largeServers.Add(server.Id); } } - foreach (var model in data.PrivateChannels) - { - var channel = AddPrivateChannel(model.Id, model.Recipient.Id); - channel.Update(model); - } + for (int i = 0; i < data.PrivateChannels.Length; i++) + AddPrivateChannel(data.PrivateChannels[i]); if (largeServers.Count > 0) GatewaySocket.SendRequestMembers(largeServers, "", 0); - if (Config.LogLevel >= LogSeverity.Verbose) - { - stopwatch.Stop(); - double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); - Logger.Verbose($"READY took {seconds} sec"); - } + else + EndConnect(); } break; @@ -515,14 +527,11 @@ namespace Discord { var server = AddServer(data.Id); server.Update(data); - - if (Config.LogEvents) - { - if (data.Unavailable != false) - Logger.Info($"Server Created: {server.Name}"); - else - Logger.Info($"Server Available: {server.Name}"); - } + + if (data.Unavailable != false) + Logger.Info($"GUILD_CREATE: {server}"); + else + Logger.Info($"GUILD_AVAILABLE: {server}"); if (data.Unavailable != false) OnJoinedServer(server); @@ -538,8 +547,7 @@ namespace Discord { var before = Config.EnablePreUpdateEvents ? server.Clone() : null; server.Update(data); - if (Config.LogEvents) - Logger.Info($"Server Updated: {server.Name}"); + Logger.Info($"GUILD_UPDATE: {server}"); OnServerUpdated(before, server); } else @@ -552,13 +560,10 @@ namespace Discord Server server = RemoveServer(data.Id); if (server != null) { - if (Config.LogEvents) - { - if (data.Unavailable != true) - Logger.Info($"Server Destroyed: {server.Name}"); - else - Logger.Info($"Server Unavailable: {server.Name}"); - } + if (data.Unavailable != true) + Logger.Info($"GUILD_DELETE: {server}"); + else + Logger.Info($"GUILD_UNAVAILABLE: {server}"); OnServerUnavailable(server); if (data.Unavailable != true) @@ -574,24 +579,22 @@ namespace Discord { var data = e.Payload.ToObject(Serializer); - Channel channel = null; + IChannel channel = null; if (data.GuildId != null) { var server = GetServer(data.GuildId.Value); if (server != null) - channel = server.AddChannel(data.Id); + channel = server.AddChannel(data, true); else + { Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); + break; + } } else - channel = AddPrivateChannel(data.Id, data.Recipient.Id); - if (channel != null) - { - channel.Update(data); - if (Config.LogEvents) - Logger.Info($"Channel Created: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); - OnChannelCreated(channel); - } + channel = AddPrivateChannel(data); + Logger.Info($"CHANNEL_CREATE: {channel}"); + OnChannelCreated(channel); } break; case "CHANNEL_UPDATE": @@ -600,10 +603,9 @@ namespace Discord var channel = GetChannel(data.Id); if (channel != null) { - var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; - channel.Update(data); - if (Config.LogEvents) - Logger.Info($"Channel Updated: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); + var before = Config.EnablePreUpdateEvents ? (channel as Channel).Clone() : null; + (channel as Channel).Update(data); + Logger.Info($"CHANNEL_UPDATE: {channel}"); OnChannelUpdated(before, channel); } else @@ -616,8 +618,7 @@ namespace Discord var channel = RemoveChannel(data.Id); if (channel != null) { - if (Config.LogEvents) - Logger.Info($"Channel Destroyed: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); + Logger.Info($"CHANNEL_DELETE: {channel}"); OnChannelDestroyed(channel); } else @@ -632,11 +633,10 @@ namespace Discord var server = GetServer(data.GuildId.Value); if (server != null) { - var user = server.AddUser(data.User.Id); + var user = server.AddUser(data, true, true); user.Update(data); user.UpdateActivity(); - if (Config.LogEvents) - Logger.Info($"User Joined: {server.Name}/{user.Name}"); + Logger.Info($"GUILD_MEMBER_ADD: {user}"); OnUserJoined(user); } else @@ -654,8 +654,7 @@ namespace Discord { var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); - if (Config.LogEvents) - Logger.Info($"User Updated: {server.Name}/{user.Name}"); + Logger.Info($"GUILD_MEMBER_UPDATE: {user}"); OnUserUpdated(before, user); } else @@ -674,8 +673,7 @@ namespace Discord var user = server.RemoveUser(data.User.Id); if (user != null) { - if (Config.LogEvents) - Logger.Info($"User Left: {server.Name}/{user.Name}"); + Logger.Info($"GUILD_MEMBER_REMOVE: {user}"); OnUserLeft(user); } else @@ -693,9 +691,21 @@ namespace Discord { foreach (var memberData in data.Members) { - var user = server.AddUser(memberData.User.Id); + var user = server.AddUser(memberData, true, false); user.Update(memberData); - //OnUserAdded(user); + } + Logger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); + + if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there + { + bool isConnectComplete = true; + foreach (var server2 in _servers.Select(x => x.Value)) + { + if (server2.CurrentUserCount < server2.UserCount) + isConnectComplete = false; + } + if (isConnectComplete) + EndConnect(); } } else @@ -711,9 +721,8 @@ namespace Discord if (server != null) { var role = server.AddRole(data.Data.Id); - role.Update(data.Data); - if (Config.LogEvents) - Logger.Info($"Role Created: {server.Name}/{role.Name}"); + role.Update(data.Data, false); + Logger.Info($"GUILD_ROLE_CREATE: {role}"); OnRoleCreated(role); } else @@ -730,9 +739,8 @@ namespace Discord if (role != null) { var before = Config.EnablePreUpdateEvents ? role.Clone() : null; - role.Update(data.Data); - if (Config.LogEvents) - Logger.Info($"Role Updated: {server.Name}/{role.Name}"); + role.Update(data.Data, true); + Logger.Info($"GUILD_ROLE_UPDATE: {role}"); OnRoleUpdated(before, role); } else @@ -751,8 +759,7 @@ namespace Discord var role = server.RemoveRole(data.RoleId); if (role != null) { - if (Config.LogEvents) - Logger.Info($"Role Deleted: {server.Name}/{role.Name}"); + Logger.Info($"GUILD_ROLE_DELETE: {role}"); OnRoleDeleted(role); } else @@ -773,8 +780,7 @@ namespace Discord var user = server.GetUser(data.User.Id); if (user != null) { - if (Config.LogEvents) - Logger.Info($"User Banned: {server.Name}/{user.Name}"); + Logger.Info($"GUILD_BAN_ADD: {user}"); OnUserBanned(user); } else @@ -790,10 +796,9 @@ namespace Discord var server = GetServer(data.GuildId.Value); if (server != null) { - var user = new User(this, data.User.Id, server); + var user = new User(data.User, this, server); user.Update(data.User); - if (Config.LogEvents) - Logger.Info($"User Unbanned: {server.Name}/{user.Name}"); + Logger.Info($"GUILD_BAN_REMOVE: {user}"); OnUserUnbanned(user); } else @@ -806,40 +811,18 @@ namespace Discord { var data = e.Payload.ToObject(Serializer); - Channel channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; if (channel != null) { - var user = channel.GetUser(data.Author.Id); + var user = (channel as Channel).GetUser(data.Author.Id); + if (user != null) { Message msg = null; - bool isAuthor = data.Author.Id == CurrentUser.Id; - //ulong nonce = 0; - - /*if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) - { - if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) - msg = _messages[nonce]; - }*/ - if (msg == null) - { - msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); - //nonce = 0; - } - - //Remapped queued message - /*if (nonce != 0) - { - msg = _messages.Remap(nonce, data.Id); - msg.Id = data.Id; - RaiseMessageSent(msg); - }*/ - - msg.Update(data); + msg = (channel as Channel).MessageManager.Add(data, user); user.UpdateActivity(); - - if (Config.LogEvents) - Logger.Verbose($"Message Received: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); + + Logger.Verbose($"MESSAGE_CREATE: {channel} ({user})"); OnMessageReceived(msg); } else @@ -852,14 +835,13 @@ namespace Discord case "MESSAGE_UPDATE": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; if (channel != null) { - var msg = channel.GetMessage(data.Id, data.Author?.Id); + var msg = (channel as Channel).MessageManager.Get(data.Id, data.Author?.Id); var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; msg.Update(data); - if (Config.LogEvents) - Logger.Verbose($"Message Update: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); + Logger.Verbose($"MESSAGE_UPDATE: {channel} ({data.Author?.Username ?? "Unknown"})"); OnMessageUpdated(before, msg); } else @@ -869,12 +851,11 @@ namespace Discord case "MESSAGE_DELETE": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; if (channel != null) { - var msg = channel.RemoveMessage(data.Id); - if (Config.LogEvents) - Logger.Verbose($"Message Deleted: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); + var msg = (channel as Channel).MessageManager.Remove(data.Id); + Logger.Verbose($"MESSAGE_DELETE: {channel} ({msg.User?.Name ?? "Unknown"})"); OnMessageDeleted(msg); } else @@ -902,14 +883,15 @@ namespace Discord break; } else - user = server.AddUser(data.User.Id); + user = server.GetUser(data.User.Id); } if (user != null) { + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"PRESENCE_UPDATE: {user}"); var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); - //Logger.Verbose($"Presence Updated: {server.Name}/{user.Name}"); OnUserUpdated(before, user); } /*else //Occurs when a user leaves a server @@ -919,28 +901,18 @@ namespace Discord case "TYPING_START": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; if (channel != null) { - User user; - if (channel.IsPrivate) - { - if (channel.Recipient.Id == data.UserId) - user = channel.Recipient; - else - return; ; - } - else - user = channel.Server.GetUser(data.UserId); + User user = (channel as Channel).GetUser(data.UserId); if (user != null) { - //Logger.Verbose($"Is Typing: {channel.Server?.Name ?? "[Private]"}/{channel.Name}/{user.Name}"); + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"TYPING_START: {user.ToString(channel)}"); OnUserIsTypingUpdated(channel, user); user.UpdateActivity(); } } - else - Logger.Warning("TYPING_START referenced an unknown channel."); } break; @@ -954,6 +926,8 @@ namespace Discord var user = server.GetUser(data.UserId); if (user != null) { + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"VOICE_STATE_UPDATE: {user}"); var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); @@ -978,8 +952,7 @@ namespace Discord PrivateUser.Update(data); foreach (var server in _servers) server.Value.CurrentUser.Update(data); - if (Config.LogEvents) - Logger.Info("Profile Updated"); + Logger.Info($"USER_UPDATE"); OnProfileUpdated(before, CurrentUser); } } @@ -995,6 +968,7 @@ namespace Discord case "VOICE_SERVER_UPDATE": case "GUILD_EMOJIS_UPDATE": case "MESSAGE_ACK": + Logger.Debug($"{e.Type} [Ignored]"); break; //Others @@ -1010,6 +984,18 @@ namespace Discord } #endregion + #region Services + public T AddService(T instance) + where T : class, IService + => _services.Add(instance); + public T AddService() + where T : class, IService, new() + => _services.Add(new T()); + public T GetService(bool isRequired = true) + where T : class, IService + => _services.Get(isRequired); + #endregion + #region Async Wrapper /// Blocking call that will execute the provided async method and wait until the client has been manually stopped. This is mainly intended for use in console applications. public void ExecuteAndWait(Func asyncAction) diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs index 453f0952d..ab6f92eba 100644 --- a/src/Discord.Net/DiscordConfig.cs +++ b/src/Discord.Net/DiscordConfig.cs @@ -16,10 +16,8 @@ namespace Discord /// Gets or sets the version of your application, used in the user agent. public string AppVersion { get; set; } = null; - /// Gets or sets the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. + /// Gets or sets the minimum log level severity that will be sent to the LogMessage event. public LogSeverity LogLevel { get; set; } = LogSeverity.Info; - /// Enables or disables the default event logger. - public bool LogEvents { get; set; } = true; //WebSocket @@ -36,10 +34,18 @@ namespace Discord public bool CacheToken { get; set; } = true; /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. public int MessageCacheSize { get; set; } = 100; - /// Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members + /// + /// Gets or sets whether the permissions cache should be used. + /// This makes operations such as User.GetPermissions(Channel), User.ServerPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage. + /// public bool UsePermissionsCache { get; set; } = true; - /// Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed. + /// Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. public bool EnablePreUpdateEvents { get; set; } = true; + /// + /// Gets or sets the max number of users a server may have for offline users to be included in the READY packet. Max is 250. + /// Decreasing this may reduce CPU usage while increasing login time and network usage. + /// + public int LargeThreshold { get; set; } = 250; //Events @@ -66,24 +72,32 @@ namespace Discord public const string InviteUrl = "https://discord.gg/"; public LogSeverity LogLevel { get; } - public bool LogEvents { get; } + public string AppName { get; } + public string AppUrl { get; } + public string AppVersion { get; } + public string UserAgent { get; } + public string CacheDir { get; } + public int ConnectionTimeout { get; } public int ReconnectDelay { get; } public int FailedReconnectDelay { get; } - public int LargeThreshold { get; } = 250; + public int LargeThreshold { get; } public int MessageCacheSize { get; } public bool UsePermissionsCache { get; } public bool EnablePreUpdateEvents { get; } - public string UserAgent { get; } - public string CacheDir { get; } internal DiscordConfig(DiscordConfigBuilder builder) { LogLevel = builder.LogLevel; - LogEvents = builder.LogEvents; + + AppName = builder.AppName; + AppUrl = builder.AppUrl; + AppVersion = builder.AppVersion; + UserAgent = GetUserAgent(builder); + CacheDir = GetCacheDir(builder); ConnectionTimeout = builder.ConnectionTimeout; ReconnectDelay = builder.ReconnectDelay; @@ -92,12 +106,9 @@ namespace Discord MessageCacheSize = builder.MessageCacheSize; UsePermissionsCache = builder.UsePermissionsCache; EnablePreUpdateEvents = builder.EnablePreUpdateEvents; - - UserAgent = GetUserAgent(builder); - CacheDir = GetCacheDir(builder); } - private string GetUserAgent(DiscordConfigBuilder builder) + private static string GetUserAgent(DiscordConfigBuilder builder) { StringBuilder sb = new StringBuilder(); if (!string.IsNullOrEmpty(builder.AppName)) @@ -119,7 +130,7 @@ namespace Discord sb.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); return sb.ToString(); } - private string GetCacheDir(DiscordConfigBuilder builder) + private static string GetCacheDir(DiscordConfigBuilder builder) { if (builder.CacheToken) return Path.Combine(Path.GetTempPath(), builder.AppName ?? "Discord.Net"); diff --git a/src/Discord.Net/ETF/ETFReader.cs b/src/Discord.Net/ETF/ETFReader.cs index d7617c344..9e1e4e8ef 100644 --- a/src/Discord.Net/ETF/ETFReader.cs +++ b/src/Discord.Net/ETF/ETFReader.cs @@ -1,26 +1,53 @@ -/*using System; +using Newtonsoft.Json; +using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; using System.Text; namespace Discord.ETF { - public class ETFReader + public class ETFReader : IDisposable { - + private static readonly ConcurrentDictionary _deserializers = new ConcurrentDictionary(); + private static readonly Dictionary _readMethods = GetPrimitiveReadMethods(); + private readonly Stream _stream; private readonly byte[] _buffer; private readonly bool _leaveOpen; private readonly Encoding _encoding; - private readonly ConcurrentDictionary _serializers, _indirectSerializers; + + public ETFReader(Stream stream, bool leaveOpen = false) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); - private void ReadNil(bool allow) + _stream = stream; + _leaveOpen = leaveOpen; + _buffer = new byte[11]; + _encoding = Encoding.UTF8; + } + + public bool ReadBool() { - if (!allow) throw new InvalidDataException(); - _stream.Read(_buffer, 0, 3); - if (_buffer[0] != 'n' || _buffer[1] != 'i' || _buffer[2] != 'l') - throw new InvalidDataException(); + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT) + { + _stream.Read(_buffer, 0, 1); + switch (_buffer[0]) //Length + { + case 4: + ReadTrue(); + return true; + case 5: + ReadFalse(); + return false; + } + } + throw new InvalidDataException(); } private void ReadTrue() { @@ -35,138 +62,430 @@ namespace Discord.ETF throw new InvalidDataException(); } - public bool? ReadBool(bool allowNil) + public int ReadSByte() { - _stream.Read(_buffer, 0, 2); - switch ((ETFType)_buffer[0]) - { - case ETFType.SMALL_ATOM_EXT: - switch (_buffer[1]) //Length - { - case 3: - ReadNil(allowNil); - return null; - case 4: - ReadTrue(); - return true; - case 5: - ReadFalse(); - return false; - } - break; - } - throw new InvalidDataException(); + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (sbyte)ReadLongInternal(type); } - - public long? ReadInteger(bool allowNil) + public uint ReadByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (byte)ReadLongInternal(type); + } + public int ReadShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (short)ReadLongInternal(type); + } + public uint ReadUShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (ushort)ReadLongInternal(type); + } + public int ReadInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (int)ReadLongInternal(type); + } + public uint ReadUInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (uint)ReadLongInternal(type); + } + public long ReadLong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return ReadLongInternal(type); + } + public ulong ReadULong() { _stream.Read(_buffer, 0, 1); - ETFType type = (ETFType)reader.ReadByte(); + ETFType type = (ETFType)_buffer[0]; + return (ulong)ReadLongInternal(type); + } + public long ReadLongInternal(ETFType type) + { switch (type) { - case ETFType.SMALL_ATOM_EXT: - ReadNil(allowNil); - return null; case ETFType.SMALL_INTEGER_EXT: _stream.Read(_buffer, 0, 1); - return (_buffer[0] << 24) | (_buffer[1] << 16) | - (_buffer[2] << 8) | (_buffer[3]); + return _buffer[0]; case ETFType.INTEGER_EXT: _stream.Read(_buffer, 0, 4); - return ??; + return (_buffer[0] << 24) | (_buffer[1] << 16) | (_buffer[2] << 8) | (_buffer[3]); case ETFType.SMALL_BIG_EXT: - return ??; + _stream.Read(_buffer, 0, 2); + bool isPositive = _buffer[0] == 0; + byte count = _buffer[1]; + + int shiftValue = (count - 1) * 8; + ulong value = 0; + _stream.Read(_buffer, 0, count); + for (int i = 0; i < count; i++, shiftValue -= 8) + value = value + _buffer[i] << shiftValue; + if (!isPositive) + return -(long)value; + else + return (long)value; } throw new InvalidDataException(); } - public void Write(sbyte value) => Write((long)value); - public void Write(byte value) => Write((ulong)value); - public void Write(short value) => Write((long)value); - public void Write(ushort value) => Write((ulong)value); - public void Write(int value) => Write((long)value); - public void Write(uint value) => Write((ulong)value); - public void Write(long value) + public float ReadSingle() { - if (value >= byte.MinValue && value <= byte.MaxValue) - { - _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; - _buffer[1] = (byte)value; - _stream.Write(_buffer, 0, 2); - } - else if (value >= int.MinValue && value <= int.MaxValue) - { - _buffer[0] = (byte)ETFType.INTEGER_EXT; - _buffer[1] = (byte)(value >> 24); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 8); - _buffer[4] = (byte)value; - _stream.Write(_buffer, 0, 5); - } - else + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (float)ReadDoubleInternal(type); + } + public double ReadDouble() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return ReadDoubleInternal(type); + } + public double ReadDoubleInternal(ETFType type) + { + throw new NotImplementedException(); + } + + public bool? ReadNullableBool() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT) { - _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; - if (value < 0) + _stream.Read(_buffer, 0, 1); + switch (_buffer[0]) //Length { - _buffer[2] = 1; //Is negative - value = -value; + case 3: + if (ReadNil()) + return null; + break; + case 4: + ReadTrue(); + return true; + case 5: + ReadFalse(); + return false; } + } + throw new InvalidDataException(); + } + public int? ReadNullableSByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (sbyte)ReadLongInternal(type); + } + public uint? ReadNullableByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (byte)ReadLongInternal(type); + } + public int? ReadNullableShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (short)ReadLongInternal(type); + } + public uint? ReadNullableUShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (ushort)ReadLongInternal(type); + } + public int? ReadNullableInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (int)ReadLongInternal(type); + } + public uint? ReadNullableUInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (uint)ReadLongInternal(type); + } + public long? ReadNullableLong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return ReadLongInternal(type); + } + public ulong? ReadNullableULong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (ulong)ReadLongInternal(type); + } + public float? ReadNullableSingle() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (float)ReadDoubleInternal(type); + } + public double? ReadNullableDouble() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return ReadDoubleInternal(type); + } + + public string ReadString() + { + throw new NotImplementedException(); + } + public byte[] ReadByteArray() + { + throw new NotImplementedException(); + } - byte bytes = 0; - while (value > 0) - _buffer[3 + bytes++] = (byte)(value >>= 8); - _buffer[1] = bytes; //Encoded bytes + public T Read() + where T : new() + { + var type = typeof(T); + var typeInfo = type.GetTypeInfo(); + var action = _deserializers.GetOrAdd(type, _ => CreateDeserializer(type, typeInfo)) as Func; + return action(this); + } + /*public void Read() + where T : Nullable + where U : struct, new() + { + }*/ + public T[] ReadArray() + { + throw new NotImplementedException(); + } + public IDictionary ReadDictionary() + { + throw new NotImplementedException(); + } + /*public object Read(object obj) + { + throw new NotImplementedException(); + }*/ - _stream.Write(_buffer, 0, 3 + bytes); + private bool ReadNil(bool ignoreLength = false) + { + if (!ignoreLength) + { + _stream.Read(_buffer, 0, 1); + byte length = _buffer[0]; + if (length != 3) return false; } + + _stream.Read(_buffer, 0, 3); + if (_buffer[0] == 'n' && _buffer[1] == 'i' && _buffer[2] == 'l') + return true; + + return false; + } + + #region Emit + private static Func CreateDeserializer(Type type, TypeInfo typeInfo) + where T : new() + { + var method = new DynamicMethod("DeserializeETF", type, new[] { typeof(ETFReader) }, true); + var generator = method.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + EmitReadValue(generator, type, typeInfo, true); + + generator.Emit(OpCodes.Ret); + return method.CreateDelegate(typeof(Func)) as Func; } - public void Write(ulong value) + private static void EmitReadValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) { - if (value <= byte.MaxValue) + //Convert enum types to their base type + if (typeInfo.IsEnum) { - _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; - _buffer[1] = (byte)value; - _stream.Write(_buffer, 0, 2); + type = Enum.GetUnderlyingType(type); + typeInfo = type.GetTypeInfo(); } - else if (value <= int.MaxValue) + //Primitives/Enums + if (!typeInfo.IsEnum && IsType(type, typeof(sbyte), typeof(byte), typeof(short), + typeof(ushort), typeof(int), typeof(uint), typeof(long), + typeof(ulong), typeof(double), typeof(bool), typeof(string), + typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), + typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), + typeof(bool?), typeof(float?), typeof(double?) + /*typeof(object), typeof(DateTime)*/)) { - _buffer[0] = (byte)ETFType.INTEGER_EXT; - _buffer[1] = (byte)(value >> 24); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 8); - _buffer[4] = (byte)value; - _stream.Write(_buffer, 0, 5); + //No conversion needed + generator.EmitCall(OpCodes.Call, GetReadMethod(type), null); } - else + //Dictionaries + /*else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + { + generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Enumerable + else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Nullable Structs + else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && + typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) { - _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; - _buffer[2] = 0; //Always positive + generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Structs/Classes + else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) + { + if (isTop) + { + typeInfo.ForEachField(f => + { + string name; + if (!f.IsPublic || !IsETFProperty(f, out name)) return; - byte bytes = 0; - while (value > 0) - _buffer[3 + bytes++] = (byte)(value >>= 8); - _buffer[1] = bytes; //Encoded bytes + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj + generator.Emit(OpCodes.Ldfld, f); //ETFReader(this), obj.fieldValue + EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); + }); - _stream.Write(_buffer, 0, 3 + bytes); + typeInfo.ForEachProperty(p => + { + string name; + if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj + generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFReader(this), obj.propValue + EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); + }); + } + else + { + //While we could drill deeper and make a large serializer that also serializes all subclasses, + //it's more efficient to serialize on a per-type basis via another Write call. + generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + }*/ + //Unsupported (decimal, char) + else + throw new InvalidOperationException($"Deserializing {type.Name} is not supported."); + } + + private static bool IsType(Type type, params Type[] types) + { + for (int i = 0; i < types.Length; i++) + { + if (type == types[i]) + return true; } + return false; + } + private static bool IsETFProperty(FieldInfo f, out string name) + { + var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) + { + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; + return true; + } + name = null; + return false; + } + private static bool IsETFProperty(PropertyInfo p, out string name) + { + var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) + { + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; + return true; + } + name = null; + return false; } - public void Write(float value) => Write((double)value); - public unsafe void Write(double value) + private static MethodInfo GetReadMethod(string name) + => typeof(ETFReader).GetTypeInfo().GetDeclaredMethods(name).Single(); + private static MethodInfo GetReadMethod(Type type) + { + MethodInfo method; + if (_readMethods.TryGetValue(type, out method)) + return method; + return null; + } + private static Dictionary GetPrimitiveReadMethods() { - ulong value2 = *(ulong*)&value; - _buffer[0] = (byte)ETFType.NEW_FLOAT_EXT; - _buffer[1] = (byte)(value2 >> 56); - _buffer[2] = (byte)(value2 >> 48); - _buffer[3] = (byte)(value2 >> 40); - _buffer[4] = (byte)(value2 >> 32); - _buffer[5] = (byte)(value2 >> 24); - _buffer[6] = (byte)(value2 >> 16); - _buffer[7] = (byte)(value2 >> 8); - _buffer[8] = (byte)value2; - _stream.Write(_buffer, 0, 9); + return new Dictionary + { + { typeof(bool), GetReadMethod(nameof(ReadBool)) }, + { typeof(bool?), GetReadMethod(nameof(ReadNullableBool)) }, + { typeof(byte), GetReadMethod(nameof(ReadByte)) }, + { typeof(byte?), GetReadMethod(nameof(ReadNullableByte)) }, + { typeof(sbyte), GetReadMethod(nameof(ReadSByte)) }, + { typeof(sbyte?), GetReadMethod(nameof(ReadNullableSByte)) }, + { typeof(short), GetReadMethod(nameof(ReadShort)) }, + { typeof(short?), GetReadMethod(nameof(ReadNullableShort)) }, + { typeof(ushort), GetReadMethod(nameof(ReadUShort)) }, + { typeof(ushort?), GetReadMethod(nameof(ReadNullableUShort)) }, + { typeof(int), GetReadMethod(nameof(ReadInt)) }, + { typeof(int?), GetReadMethod(nameof(ReadNullableInt)) }, + { typeof(uint), GetReadMethod(nameof(ReadUInt)) }, + { typeof(uint?), GetReadMethod(nameof(ReadNullableUInt)) }, + { typeof(long), GetReadMethod(nameof(ReadLong)) }, + { typeof(long?), GetReadMethod(nameof(ReadNullableLong)) }, + { typeof(ulong), GetReadMethod(nameof(ReadULong)) }, + { typeof(ulong?), GetReadMethod(nameof(ReadNullableULong)) }, + { typeof(float), GetReadMethod(nameof(ReadSingle)) }, + { typeof(float?), GetReadMethod(nameof(ReadNullableSingle)) }, + { typeof(double), GetReadMethod(nameof(ReadDouble)) }, + { typeof(double?), GetReadMethod(nameof(ReadNullableDouble)) }, + }; + } + #endregion + + #region IDisposable + private bool _isDisposed = false; + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + if (_leaveOpen) + _stream.Flush(); + else + _stream.Dispose(); + } + _isDisposed = true; + } } - public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); + public void Dispose() => Dispose(true); + #endregion } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/Discord.Net/ETF/ETFWriter.cs b/src/Discord.Net/ETF/ETFWriter.cs index 43255780e..37d1553db 100644 --- a/src/Discord.Net/ETF/ETFWriter.cs +++ b/src/Discord.Net/ETF/ETFWriter.cs @@ -12,21 +12,23 @@ namespace Discord.ETF { public unsafe class ETFWriter : IDisposable { - private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; - private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; - private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; + private static readonly ConcurrentDictionary _serializers = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _indirectSerializers = new ConcurrentDictionary(); - private readonly static MethodInfo _writeTMethod = GetGenericWriteMethod(null); - private readonly static MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); - private readonly static MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); - private readonly static MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); - private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; + private static readonly byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; + private static readonly byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; + + private static readonly MethodInfo _writeTMethod = GetGenericWriteMethod(null); + private static readonly MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); + private static readonly MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); + private static readonly MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); + private static readonly DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private readonly Stream _stream; private readonly byte[] _buffer; private readonly bool _leaveOpen; private readonly Encoding _encoding; - private readonly ConcurrentDictionary _serializers, _indirectSerializers; public virtual Stream BaseStream { @@ -45,8 +47,6 @@ namespace Discord.ETF _leaveOpen = leaveOpen; _buffer = new byte[11]; _encoding = Encoding.UTF8; - _serializers = new ConcurrentDictionary(); - _indirectSerializers = new ConcurrentDictionary(); } public void Write(bool value) @@ -72,6 +72,7 @@ namespace Discord.ETF } else if (value >= int.MinValue && value <= int.MaxValue) { + //TODO: Does this encode negatives correctly? _buffer[0] = (byte)ETFType.INTEGER_EXT; _buffer[1] = (byte)(value >> 24); _buffer[2] = (byte)(value >> 16); @@ -145,7 +146,7 @@ namespace Discord.ETF public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); - public void Write(bool? value) { if (value.HasValue) Write((bool)value.Value); else WriteNil(); } + public void Write(bool? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } @@ -158,35 +159,35 @@ namespace Discord.ETF public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } - public void Write(byte[] value) + public void Write(string value) { if (value != null) { - int count = value.Length; + var bytes = _encoding.GetBytes(value); + int count = bytes.Length; _buffer[0] = (byte)ETFType.BINARY_EXT; _buffer[1] = (byte)(count >> 24); _buffer[2] = (byte)(count >> 16); _buffer[3] = (byte)(count >> 8); _buffer[4] = (byte)count; _stream.Write(_buffer, 0, 5); - _stream.Write(value, 0, value.Length); + _stream.Write(bytes, 0, bytes.Length); } else WriteNil(); } - public void Write(string value) + public void Write(byte[] value) { if (value != null) { - var bytes = _encoding.GetBytes(value); - int count = bytes.Length; + int count = value.Length; _buffer[0] = (byte)ETFType.BINARY_EXT; _buffer[1] = (byte)(count >> 24); _buffer[2] = (byte)(count >> 16); _buffer[3] = (byte)(count >> 8); _buffer[4] = (byte)count; _stream.Write(_buffer, 0, 5); - _stream.Write(bytes, 0, bytes.Length); + _stream.Write(value, 0, value.Length); } else WriteNil(); @@ -269,7 +270,7 @@ namespace Discord.ETF public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); #region Emit - private Action CreateSerializer(Type type, TypeInfo typeInfo, bool isDirect) + private static Action CreateSerializer(Type type, TypeInfo typeInfo, bool isDirect) { var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); @@ -291,7 +292,7 @@ namespace Discord.ETF generator.Emit(OpCodes.Ret); return method.CreateDelegate(typeof(Action)) as Action; } - private void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) + private static void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) { //Convert enum types to their base type if (typeInfo.IsEnum) @@ -335,18 +336,21 @@ namespace Discord.ETF //Dictionaries else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + { generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); - + } //Enumerable else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); - + } //Nullable Structs - else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && + else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) + { generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); - + } //Structs/Classes else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) { @@ -387,7 +391,6 @@ namespace Discord.ETF generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); } } - //Unsupported (decimal, char) else throw new InvalidOperationException($"Serializing {type.Name} is not supported."); @@ -429,7 +432,7 @@ namespace Discord.ETF { return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) .Where(x => x.GetParameters()[0].ParameterType == paramType) - .FirstOrDefault(); + .Single(); } private static MethodInfo GetGenericWriteMethod(Type genericType) { diff --git a/src/Discord.Net/Entities/Channel.cs b/src/Discord.Net/Entities/Channel.cs new file mode 100644 index 000000000..5894ade45 --- /dev/null +++ b/src/Discord.Net/Entities/Channel.cs @@ -0,0 +1,55 @@ +using APIChannel = Discord.API.Client.Channel; +using System.Collections.Generic; + +namespace Discord +{ + public abstract class Channel : IChannel + { + /// An entry in a public channel's permissions that gives or takes permissions from a specific role or user. + public class PermissionRule + { + /// The type of object TargetId is referring to. + public PermissionTarget TargetType { get; } + /// The Id of an object, whos type is specified by TargetType, that is the target of permissions being added or taken away. + public ulong TargetId { get; } + /// A collection of permissions that are added or taken away from the target. + public ChannelTriStatePermissions Permissions { get; } + + internal PermissionRule(PermissionTarget targetType, ulong targetId, uint allow, uint deny) + { + TargetType = targetType; + TargetId = targetId; + Permissions = new ChannelTriStatePermissions(allow, deny); + } + } + + /// Gets the unique identifier for this channel. + public ulong Id { get; } + + public abstract DiscordClient Client { get; } + /// Gets the type of this channel. + public abstract ChannelType Type { get; } + public bool IsText => (Type & ChannelType.Text) != 0; + public bool IsVoice => (Type & ChannelType.Voice) != 0; + public bool IsPrivate => (Type & ChannelType.Private) != 0; + public bool IsPublic => (Type & ChannelType.Public) != 0; + + public abstract User CurrentUser { get; } + /// Gets a collection of all users in this channel. + public abstract IEnumerable Users { get; } + + internal abstract MessageManager MessageManager { get; } + internal abstract PermissionManager PermissionManager { get; } + + protected Channel(ulong id) + { + Id = id; + } + + internal abstract void Update(APIChannel model); + + internal abstract User GetUser(ulong id); + + internal abstract Channel Clone(); + } +} diff --git a/src/Discord.Net/Entities/Color.cs b/src/Discord.Net/Entities/Color.cs new file mode 100644 index 000000000..325aa2f39 --- /dev/null +++ b/src/Discord.Net/Entities/Color.cs @@ -0,0 +1,24 @@ +namespace Discord +{ + public class Color + { + public static readonly Color Default = new Color(0); + + public uint RawValue { get; } + + public Color(uint rawValue) { RawValue = rawValue; } + public Color(byte r, byte g, byte b) : this(((uint)r << 16) | ((uint)g << 8) | b) { } + public Color(float r, float g, float b) : this((byte)(r * 255.0f), (byte)(g * 255.0f), (byte)(b * 255.0f)) { } + + /// Gets or sets the red component for this color. + public byte R => (byte)(RawValue >> 16); + /// Gets or sets the green component for this color. + public byte G => (byte)(RawValue >> 8); + /// Gets or sets the blue component for this color. + public byte B => (byte)(RawValue); + + private byte GetByte(int pos) => (byte)(RawValue >> (8 * (pos - 1))); + + public override string ToString() => '#' + RawValue.ToString("X"); + } +} diff --git a/src/Discord.Net/Entities/IChannel.cs b/src/Discord.Net/Entities/IChannel.cs new file mode 100644 index 000000000..f39678abb --- /dev/null +++ b/src/Discord.Net/Entities/IChannel.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Discord +{ + public interface IChannel + { + /// Gets the unique identifier for this channel. + ulong Id { get; } + DiscordClient Client { get; } + + /// Gets the type of this channel. + ChannelType Type { get; } + bool IsText { get; } + bool IsVoice { get; } + bool IsPrivate { get; } + bool IsPublic { get; } + + /// Gets a collection of all users in this channel. + IEnumerable Users { get; } + } +} diff --git a/src/Discord.Net/Entities/IPrivateChannel.cs b/src/Discord.Net/Entities/IPrivateChannel.cs new file mode 100644 index 000000000..44d55a67f --- /dev/null +++ b/src/Discord.Net/Entities/IPrivateChannel.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + public interface IPrivateChannel : IChannel + { + User Recipient { get; } + } +} diff --git a/src/Discord.Net/Entities/IPublicChannel.cs b/src/Discord.Net/Entities/IPublicChannel.cs new file mode 100644 index 000000000..32b8c610e --- /dev/null +++ b/src/Discord.Net/Entities/IPublicChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IPublicChannel : IChannel + { + } +} diff --git a/src/Discord.Net/Entities/ITextChannel.cs b/src/Discord.Net/Entities/ITextChannel.cs new file mode 100644 index 000000000..af665ab0f --- /dev/null +++ b/src/Discord.Net/Entities/ITextChannel.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public interface ITextChannel : IChannel + { + Message GetMessage(ulong id); + Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before); + + Task SendMessage(string text, bool isTTS = false); + Task SendFile(string filePath); + Task SendFile(string filename, Stream stream); + + Task SendIsTyping(); + } +} diff --git a/src/Discord.Net/Entities/IVoiceChannel.cs b/src/Discord.Net/Entities/IVoiceChannel.cs new file mode 100644 index 000000000..7c3f2c194 --- /dev/null +++ b/src/Discord.Net/Entities/IVoiceChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IVoiceChannel : IChannel + { + } +} diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Entities/Invite.cs similarity index 78% rename from src/Discord.Net/Models/Invite.cs rename to src/Discord.Net/Entities/Invite.cs index 56df207d9..a1b27ce4a 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Entities/Invite.cs @@ -1,10 +1,10 @@ -using Discord.API.Client; +using APIInvite = Discord.API.Client.Invite; +using Discord.API.Client; using Discord.API.Client.Rest; using Discord.Net; using System; using System.Net; using System.Threading.Tasks; -using APIInvite = Discord.API.Client.Invite; namespace Discord { @@ -84,24 +84,28 @@ namespace Discord public bool IsTemporary { get; private set; } /// Gets when this invite was created. public DateTime CreatedAt { get; private set; } + + /// Returns a URL for this invite using XkcdCode if available or Id if not. + public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; - /// Returns a URL for this invite using XkcdCode if available or Id if not. - public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; - - internal Invite(DiscordClient client, string code, string xkcdPass) + internal Invite(APIInvite model, DiscordClient client) + : this(model.Code, model.XkcdPass) { Client = client; + Update(model); + } + internal Invite(InviteReference model, DiscordClient client) + : this(model.Code, model.XkcdPass) + { + Client = client; + Update(model); + } + private Invite(string code, string xkcdCode) + { Code = code; - XkcdCode = xkcdPass; - } - - internal void Update(InviteReference model) - { - if (model.Guild != null) - Server = new ServerInfo(model.Guild.Id, model.Guild.Name); - if (model.Channel != null) - Channel = new ChannelInfo(model.Channel.Id, model.Channel.Name); + XkcdCode = xkcdCode; } + internal void Update(APIInvite model) { Update(model as InviteReference); @@ -118,8 +122,15 @@ namespace Discord Uses = model.Uses.Value; if (model.CreatedAt != null) CreatedAt = model.CreatedAt.Value; - } - + } + internal void Update(InviteReference model) + { + if (model.Guild != null) + Server = new ServerInfo(model.Guild.Id, model.Guild.Name); + if (model.Channel != null) + Channel = new ChannelInfo(model.Channel.Id, model.Channel.Name); + } + public async Task Delete() { try { await Client.ClientAPI.Send(new DeleteInviteRequest(Code)).ConfigureAwait(false); } @@ -130,12 +141,11 @@ namespace Discord internal Invite Clone() { - var result = new Invite(); + var result = new Invite(Code, XkcdCode); _cloner(this, result); return result; } - private Invite() { } //Used for cloning - public override string ToString() => XkcdCode ?? Code; + public override string ToString() => $"{Server}/{XkcdCode ?? Code}"; } } diff --git a/src/Discord.Net/Entities/Managers/MessageManager.cs b/src/Discord.Net/Entities/Managers/MessageManager.cs new file mode 100644 index 000000000..14b569750 --- /dev/null +++ b/src/Discord.Net/Entities/Managers/MessageManager.cs @@ -0,0 +1,146 @@ +using APIMessage = Discord.API.Client.Message; +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + internal class MessageManager : IEnumerable + { + private readonly ITextChannel _channel; + private readonly int _size; + private readonly ConcurrentDictionary _messages; + private readonly ConcurrentQueue _orderedMessages; + + public MessageManager(ITextChannel channel, int size = 0) + { + _channel = channel; + _size = size; + if (size > 0) + { + _messages = new ConcurrentDictionary(2, size); + _orderedMessages = new ConcurrentQueue(); + } + } + + internal Message Add(APIMessage model, User user) => Add(new Message(model, _channel, user)); + internal Message Add(ulong id, User user) => Add(new Message(id, _channel, user)); + private Message Add(Message message) + { + message.State = MessageState.Normal; + if (_size > 0) + { + if (_messages.TryAdd(message.Id, message)) + { + _orderedMessages.Enqueue(message.Id); + + ulong msgId; + while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) + { + Message msg; + if (_messages.TryRemove(msgId, out msg)) + msg.State = MessageState.Detached; + } + } + } + return message; + } + internal Message Remove(ulong id) + { + if (_size > 0) + { + Message msg; + if (_messages.TryRemove(id, out msg)) + return msg; + } + return new Message(id, _channel, null) { State = MessageState.Deleted }; + } + + public Message Get(ulong id, ulong? userId = null) + { + if (_messages != null) + { + Message result; + if (_messages.TryGetValue(id, out result)) + return result; + } + return new Message(id, _channel, userId != null ? (_channel as Channel).GetUser(userId.Value) : null) { State = MessageState.Detached }; + } + + public async Task Download(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); + if (limit == 0) return new Message[0]; + + try + { + var request = new GetMessagesRequest(_channel.Id) + { + Limit = limit, + RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null, + RelativeId = relativeMessageId ?? 0 + }; + var msgs = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false); + var server = (_channel as PublicChannel)?.Server; + + return msgs.Select(x => + { + Message msg = null; + ulong id = x.Author.Id; + var user = server?.GetUser(id) ?? (_channel as Channel).GetUser(id); + msg = new Message(x, _channel, user); + return msg; + }).ToArray(); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) + { + return new Message[0]; + } + } + + public Task Send(string text, bool isTTS) + { + if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); + if (text.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); + return Task.FromResult(_channel.Client.MessageQueue.QueueSend(_channel, text, isTTS)); + } + public async Task SendFile(string filePath) + { + using (var stream = File.OpenRead(filePath)) + return await SendFile(Path.GetFileName(filePath), stream).ConfigureAwait(false); + } + public async Task SendFile(string filename, Stream stream) + { + var request = new SendFileRequest(_channel.Id) + { + Filename = filename, + Stream = stream + }; + var response = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false); + + return Add(response, (_channel as Channel).CurrentUser); + } + + public IEnumerator GetEnumerator() + { + return _orderedMessages + .Select(x => + { + Message msg; + if (_messages.TryGetValue(x, out msg)) + return msg; + return null; + }) + .Where(x => x != null).GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Discord.Net/Entities/Managers/PermissionManager.cs b/src/Discord.Net/Entities/Managers/PermissionManager.cs new file mode 100644 index 000000000..4b8eb5c8c --- /dev/null +++ b/src/Discord.Net/Entities/Managers/PermissionManager.cs @@ -0,0 +1,223 @@ +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using APIChannel = Discord.API.Client.Channel; + +namespace Discord +{ + internal class PermissionManager + { + public struct Member + { + public User User { get; } + public ChannelPermissions Permissions { get; } + + public Member(User user, ChannelPermissions permissions) + { + User = user; + Permissions = permissions; + } + } + + private readonly PublicChannel _channel; + private readonly ConcurrentDictionary _users; + private Dictionary _rules; + + public IEnumerable Users => _users.Select(x => x.Value); + public IEnumerable Rules => _rules.Values; + + public PermissionManager(PublicChannel channel, APIChannel model, int initialSize = -1) + { + _channel = channel; + if (initialSize >= 0) + _users = new ConcurrentDictionary(2, initialSize); + Update(model); + } + + public void Update(APIChannel model) + { + _rules = model.PermissionOverwrites + .Select(x => new Channel.PermissionRule(EnumConverters.ToPermissionTarget(x.Type), x.Id, x.Allow, x.Deny)) + .ToDictionary(x => x.TargetId); + UpdatePermissions(); + } + + public ChannelTriStatePermissions? GetOverwrite(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + Channel.PermissionRule rule; + if (_rules.TryGetValue(user.Id, out rule)) + return rule.Permissions; + return null; + } + public ChannelTriStatePermissions? GetOverwrite(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + Channel.PermissionRule rule; + if (_rules.TryGetValue(role.Id, out rule)) + return rule.Permissions; + return null; + } + public Task AddOrUpdateOverwrite(User user, ChannelTriStatePermissions permissions) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return AddOrUpdateOverwrite(user.Id, PermissionTarget.User, permissions); + } + public Task AddOrUpdateOverwrite(Role role, ChannelTriStatePermissions permissions) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + return AddOrUpdateOverwrite(role.Id, PermissionTarget.Role, permissions); + } + private Task AddOrUpdateOverwrite(ulong id, PermissionTarget type, ChannelTriStatePermissions permissions) + { + var request = new AddOrUpdateChannelPermissionsRequest(id) + { + TargetId = id, + TargetType = EnumConverters.ToString(type), + Allow = permissions.AllowValue, + Deny = permissions.DenyValue + }; + return _channel.Client.ClientAPI.Send(request); + } + public Task RemoveOverwrite(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return RemoveOverwrite(user.Id); + } + public Task RemoveOverwrite(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + return RemoveOverwrite(role.Id); + } + private async Task RemoveOverwrite(ulong id) + { + try { await _channel.Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(_channel.Id, id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + public ChannelPermissions GetPermissions(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + if (_users != null) + { + Member member; + if (_users.TryGetValue(user.Id, out member)) + return member.Permissions; + else + return ChannelPermissions.None; + } + else + { + ChannelPermissions perms = new ChannelPermissions(); + ResolvePermissions(user, ref perms); + return perms; + } + } + public void UpdatePermissions() + { + if (_users != null) + { + foreach (var pair in _users) + { + var member = pair.Value; + var perms = member.Permissions; + if (ResolvePermissions(member.User, ref perms)) + _users[pair.Key] = new Member(member.User, perms); + } + } + } + public void UpdatePermissions(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + if (_users != null) + { + Member member; + if (_users.TryGetValue(user.Id, out member)) + { + var perms = member.Permissions; + if (ResolvePermissions(member.User, ref perms)) + _users[user.Id] = new Member(member.User, perms); + } + } + } + public bool ResolvePermissions(User user, ref ChannelPermissions permissions) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + uint newPermissions = 0; + var server = user.Server; + + var mask = ChannelPermissions.All(_channel.Type).RawValue; + if (_channel.IsPrivate || user.IsOwner) + newPermissions = mask; //Private messages and owners always have all permissions + else + { + //Start with this user's server permissions + newPermissions = server.GetPermissions(user).RawValue; + var rules = _rules; + + Channel.PermissionRule rule; + var roles = user.Roles.ToArray(); + if (roles.Length > 0) + { + for (int i = 0; i < roles.Length; i++) + { + if (rules.TryGetValue(roles[i].Id, out rule)) + newPermissions &= ~rule.Permissions.DenyValue; + } + for (int i = 0; i < roles.Length; i++) + { + if (rules.TryGetValue(roles[i].Id, out rule)) + newPermissions |= rule.Permissions.AllowValue; + } + } + if (rules.TryGetValue(user.Id, out rule)) + newPermissions = (newPermissions & ~rule.Permissions.DenyValue) | rule.Permissions.AllowValue; + + if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions)) + newPermissions = mask; //ManageRolesOrPermissions gives all permisions + else if (_channel.IsText && !newPermissions.HasBit((byte)PermissionBits.ReadMessages)) + newPermissions = 0; //No read permission on a text channel removes all other permissions + else if (_channel.IsVoice && !newPermissions.HasBit((byte)PermissionBits.Connect)) + newPermissions = 0; //No connect permissions on a voice channel removes all other permissions + else + newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example) + } + + if (newPermissions != permissions.RawValue) + { + permissions = new ChannelPermissions(newPermissions); + return true; + } + return false; + } + + public void AddUser(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + if (_users != null) + { + var perms = new ChannelPermissions(); + ResolvePermissions(user, ref perms); + var member = new Member(user, ChannelPermissions.None); + _users[user.Id] = new Member(user, ChannelPermissions.None); + } + } + public void RemoveUser(ulong id) + { + Member ignored; + if (_users != null) + _users.TryRemove(id, out ignored); + } + } +} diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Entities/Message.cs similarity index 77% rename from src/Discord.Net/Models/Message.cs rename to src/Discord.Net/Entities/Message.cs index c589df91e..a812cd897 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Entities/Message.cs @@ -7,18 +7,6 @@ using APIMessage = Discord.API.Client.Message; namespace Discord { - public enum MessageState : byte - { - /// Message did not originate from this session, or was successfully sent. - Normal = 0, - /// Message is current queued. - Queued, - /// Message was deleted before it was sent. - Aborted, - /// Message failed to be sent. - Failed - } - public class Message { private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); @@ -29,7 +17,7 @@ namespace Discord private static readonly Attachment[] _initialAttachments = new Attachment[0]; private static readonly Embed[] _initialEmbeds = new Embed[0]; - internal static string CleanUserMentions(Channel channel, string text, List users = null) + internal static string CleanUserMentions(PublicChannel channel, string text, List users = null) { return _userRegex.Replace(text, new MatchEvaluator(e => { @@ -47,7 +35,7 @@ namespace Discord return e.Value; //User not found or parse failed })); } - internal static string CleanChannelMentions(Channel channel, string text, List channels = null) + internal static string CleanChannelMentions(PublicChannel channel, string text, List channels = null) { var server = channel.Server; if (server == null) return text; @@ -81,33 +69,21 @@ namespace Discord })); }*/ - private static string Resolve(Channel channel, string text) + internal static string ResolveMentions(IChannel channel, string text) { + if (channel == null) throw new ArgumentNullException(nameof(channel)); if (text == null) throw new ArgumentNullException(nameof(text)); - var client = channel.Client; - text = CleanUserMentions(channel, text); - text = CleanChannelMentions(channel, text); - //text = CleanRoleMentions(Channel, text); + var publicChannel = channel as PublicChannel; + if (publicChannel != null) + { + text = CleanUserMentions(publicChannel, text); + text = CleanChannelMentions(publicChannel, text); + //text = CleanRoleMentions(publicChannel, text); + } return text; } - /*internal class ImportResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - if (member is PropertyInfo) - { - if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite) - return null; - - property.Writable = true; //Handles private setters - } - return property; - } - }*/ - public class Attachment : File { /// Unique identifier for this file. @@ -171,12 +147,12 @@ namespace Discord /// Returns the unique identifier for this message. public ulong Id { get; internal set; } /// Returns the channel this message was sent to. - public Channel Channel { get; } + public ITextChannel Channel { get; } /// Returns the author of this message. public User User { get; } /// Returns true if the message was sent as text-to-speech by someone with permissions to do so. - public bool IsTTS { get; private set; } + public bool IsTTS { get; internal set; } /// Returns the state of this message. Only useful if UseMessageQueue is true. public MessageState State { get; internal set; } /// Returns the raw content of this message as it was received from the server. @@ -195,31 +171,34 @@ namespace Discord /// Returns a collection of all users mentioned in this message. public IEnumerable MentionedUsers { get; internal set; } /// Returns a collection of all channels mentioned in this message. - public IEnumerable MentionedChannels { get; internal set; } + public IEnumerable MentionedChannels { get; internal set; } /// Returns a collection of all roles mentioned in this message. public IEnumerable MentionedRoles { get; internal set; } internal int Nonce { get; set; } - + /// Returns the server containing the channel this message was sent to. - public Server Server => Channel.Server; + public Server Server => (Channel as PublicChannel)?.Server; /// Returns if this message was sent from the logged-in accounts. public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id; - internal Message(ulong id, Channel channel, User user) + internal Message(APIMessage model, ITextChannel channel, User user) + : this(model.Id, channel, user) { + Update(model); + } + internal Message(ulong id, ITextChannel channel, User user) + { Id = id; Channel = channel; User = user; - - Attachments = _initialAttachments; - Embeds = _initialEmbeds; - } + Attachments = _initialAttachments; + Embeds = _initialEmbeds; + } internal void Update(APIMessage model) { var channel = Channel; - var server = channel.Server; if (model.Attachments != null) { Attachments = model.Attachments @@ -274,36 +253,34 @@ namespace Discord if (model.Mentions != null) { MentionedUsers = model.Mentions - .Select(x => Channel.GetUser(x.Id)) + .Select(x => (Channel as Channel).GetUser(x.Id)) .Where(x => x != null) .ToArray(); } if (model.IsMentioningEveryone != null) - { - if (model.IsMentioningEveryone.Value && User != null && User.GetPermissions(channel).MentionEveryone) - MentionedRoles = new Role[] { Server.EveryoneRole }; - else - MentionedRoles = new Role[0]; + { + var server = (channel as PublicChannel).Server; + if (model.IsMentioningEveryone.Value && server != null) + MentionedRoles = new Role[] { server.EveryoneRole }; + else + MentionedRoles = Enumerable.Empty(); } if (model.Content != null) { string text = model.Content; RawText = text; - //var mentionedUsers = new List(); - var mentionedChannels = new List(); - //var mentionedRoles = new List(); - text = CleanUserMentions(Channel, text/*, mentionedUsers*/); - if (server != null) - { - text = CleanChannelMentions(Channel, text, mentionedChannels); - //text = CleanRoleMentions(_client, User, channel, text, mentionedRoles); - } - Text = text; - - //MentionedUsers = mentionedUsers; - MentionedChannels = mentionedChannels; - //MentionedRoles = mentionedRoles; + List mentionedChannels = null; + if (Channel.IsPublic) + mentionedChannels = new List(); + + text = CleanUserMentions(Channel as PublicChannel, text); + text = CleanChannelMentions(Channel as PublicChannel, text, mentionedChannels); + + if (Channel.IsPublic) + MentionedChannels = mentionedChannels; + + Text = text; } } @@ -338,21 +315,13 @@ namespace Discord return MentionedUsers?.Contains(me) ?? false; } - /// Resolves all mentions in a provided string to those users, channels or roles' names. - public string Resolve(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - return Resolve(Channel, text); - } - internal Message Clone() { - var result = new Message(); + var result = new Message(Id, Channel, User); _cloner(this, result); return result; } - private Message() { } //Used for cloning - public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; + public override string ToString() => $"{User}: {RawText}"; } } diff --git a/src/Discord.Net/Models/Permissions.cs b/src/Discord.Net/Entities/Permissions.cs similarity index 95% rename from src/Discord.Net/Models/Permissions.cs rename to src/Discord.Net/Entities/Permissions.cs index 609524dfe..badccc116 100644 --- a/src/Discord.Net/Models/Permissions.cs +++ b/src/Discord.Net/Entities/Permissions.cs @@ -97,6 +97,8 @@ namespace Discord RawValue = value; } public ServerPermissions(uint rawValue) { RawValue = rawValue; } + + public override string ToString() => Convert.ToString(RawValue, 2); } public struct ChannelPermissions @@ -105,13 +107,15 @@ namespace Discord public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); - public static ChannelPermissions All(Channel channel) => All(channel.Type, channel.IsPrivate); - public static ChannelPermissions All(ChannelType channelType, bool isPrivate) + public static ChannelPermissions All(ChannelType channelType) { - if (isPrivate) return PrivateOnly; - else if (channelType == ChannelType.Text) return TextOnly; - else if (channelType == ChannelType.Voice) return VoiceOnly; - else return None; + switch (channelType) + { + case ChannelType.Text: return TextOnly; + case ChannelType.Voice: return VoiceOnly; + case ChannelType.Private: return PrivateOnly; + default: return None; + } } public uint RawValue { get; } @@ -191,11 +195,13 @@ namespace Discord RawValue = value; } public ChannelPermissions(uint rawValue) { RawValue = rawValue; } + + public override string ToString() => Convert.ToString(RawValue, 2); } - public struct ChannelPermissionOverrides + public struct ChannelTriStatePermissions { - public static ChannelPermissionOverrides InheritAll { get; } = new ChannelPermissionOverrides(); + public static ChannelTriStatePermissions InheritAll { get; } = new ChannelTriStatePermissions(); public uint AllowValue { get; } public uint DenyValue { get; } @@ -236,16 +242,16 @@ namespace Discord /// If True, a user may use voice activation rather than push-to-talk. public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation); - public ChannelPermissionOverrides(PermValue? createInstantInvite = null, PermValue? managePermissions = null, + public ChannelTriStatePermissions(PermValue? createInstantInvite = null, PermValue? managePermissions = null, PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null) - : this(new ChannelPermissionOverrides(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages, + : this(new ChannelTriStatePermissions(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation) { } - public ChannelPermissionOverrides(ChannelPermissionOverrides basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null, + public ChannelTriStatePermissions(ChannelTriStatePermissions basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null, PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, @@ -274,11 +280,13 @@ namespace Discord AllowValue = allow; DenyValue = deny; } - public ChannelPermissionOverrides(uint allow = 0, uint deny = 0) + public ChannelTriStatePermissions(uint allow = 0, uint deny = 0) { AllowValue = allow; DenyValue = deny; } + + public override string ToString() => $"Allow: {Convert.ToString(AllowValue, 2)}, Deny: {Convert.ToString(DenyValue, 2)}"; } internal static class PermissionsHelper { diff --git a/src/Discord.Net/Entities/PrivateChannel.cs b/src/Discord.Net/Entities/PrivateChannel.cs new file mode 100644 index 000000000..b3f3f1faf --- /dev/null +++ b/src/Discord.Net/Entities/PrivateChannel.cs @@ -0,0 +1,66 @@ +using APIChannel = Discord.API.Client.Channel; +using Discord.API.Client.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public class PrivateChannel : Channel, IPrivateChannel, ITextChannel + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + private readonly MessageManager _messages; + + /// Gets the target user, if this is a private chat. + public User Recipient { get; } + + public override DiscordClient Client => Recipient.Client; + + public override ChannelType Type => ChannelType.Private; + public override User CurrentUser => Client.PrivateUser; + public override IEnumerable Users => new User[] { Client.PrivateUser, Recipient }; + + internal override MessageManager MessageManager => _messages; + internal override PermissionManager PermissionManager => null; + + internal PrivateChannel(ulong id, User recipient, APIChannel model) + : this(id, recipient) + { + _messages = new MessageManager(this, Client.Config.MessageCacheSize); + Update(model); + } + private PrivateChannel(ulong id, User recipient) + :base(id) + { + Recipient = recipient; + } + + internal override User GetUser(ulong id) + { + if (id == Recipient.Id) return Recipient; + else if (id == Client.CurrentUser.Id) return Client.PrivateUser; + else return null; + } + + public Message GetMessage(ulong id) => _messages.Get(id); + public Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) + => _messages.Download(limit, relativeMessageId, relativeDir); + + public Task SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); + public Task SendFile(string filePath) => _messages.SendFile(filePath); + public Task SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream); + public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); + + public override string ToString() => $"@{Recipient.Name}"; + + internal override void Update(APIChannel model) { } + internal override Channel Clone() + { + var result = new PrivateChannel(Id, Recipient); + _cloner(this, result); + return result; + } + } +} diff --git a/src/Discord.Net/Models/Profile.cs b/src/Discord.Net/Entities/Profile.cs similarity index 90% rename from src/Discord.Net/Models/Profile.cs rename to src/Discord.Net/Entities/Profile.cs index c0aaa6f25..7029e4c41 100644 --- a/src/Discord.Net/Models/Profile.cs +++ b/src/Discord.Net/Entities/Profile.cs @@ -1,4 +1,5 @@ -using Discord.API.Client.Rest; +using Discord.API.Client; +using Discord.API.Client.Rest; using System; using System.IO; using System.Threading.Tasks; @@ -28,16 +29,20 @@ namespace Discord public UserStatus Status => Client.PrivateUser.Status; /// Returns the string used to mention this user. public string Mention => $"<@{Id}>"; - + /// Gets the email for this user. public string Email { get; private set; } /// Gets if the email for this user has been verified. public bool? IsVerified { get; private set; } - internal Profile(DiscordClient client, ulong id) + internal Profile(UserReference model, DiscordClient client) + : this(model.Id, client) + { + } + private Profile(ulong id, DiscordClient client) { - Client = client; Id = id; + Client = client; } internal void Update(APIUser model) @@ -77,12 +82,11 @@ namespace Discord internal Profile Clone() { - var result = new Profile(); + var result = new Profile(Id, Client); _cloner(this, result); return result; } - private Profile() { } //Used for cloning - public override string ToString() => Id.ToIdString(); + public override string ToString() => Name; } } diff --git a/src/Discord.Net/Entities/PublicChannel.cs b/src/Discord.Net/Entities/PublicChannel.cs new file mode 100644 index 000000000..a1d5c4f41 --- /dev/null +++ b/src/Discord.Net/Entities/PublicChannel.cs @@ -0,0 +1,109 @@ +using APIChannel = Discord.API.Client.Channel; +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + /// A public Discord channel + public abstract class PublicChannel : Channel, IModel, IMentionable + { + internal readonly PermissionManager _permissions; + + /// Gets the server owning this channel. + public Server Server { get; } + + /// Gets or sets the name of this channel. + public string Name { get; set; } + /// Getsor sets the position of this channel relative to other channels of the same type in this server. + public int Position { get; set; } + + /// Gets the DiscordClient that created this model. + public override DiscordClient Client => Server.Client; + public override User CurrentUser => Server.CurrentUser; + /// Gets the string used to mention this channel. + public string Mention => $"<#{Id}>"; + /// Gets a collection of all custom permissions used for this channel. + public IEnumerable PermissionRules => _permissions.Rules; + + internal PublicChannel(APIChannel model, Server server) + : this(model.Id, server) + { + _permissions = new PermissionManager(this, model, server.Client.Config.UsePermissionsCache ? (int)(server.UserCount * 1.05) : -1); + Update(model); + } + protected PublicChannel(ulong id, Server server) + : base(id) + { + Server = server; + } + + internal override void Update(APIChannel model) + { + if (model.Name != null) Name = model.Name; + if (model.Position != null) Position = model.Position.Value; + + if (model.PermissionOverwrites != null) + _permissions.Update(model); + } + + public async Task Delete() + { + try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + public abstract Task Save(); + + internal override User GetUser(ulong id) => Server.GetUser(id); + + public ChannelTriStatePermissions? GetPermissionsRule(User user) => _permissions.GetOverwrite(user); + public ChannelTriStatePermissions? GetPermissionsRule(Role role) => _permissions.GetOverwrite(role); + public Task AddOrUpdatePermissionsRule(User user, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(user, permissions); + public Task AddOrUpdatePermissionsRule(Role role, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(role, permissions); + public Task RemovePermissionsRule(User user) => _permissions.RemoveOverwrite(user); + public async Task RemovePermissionsRule(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + try { await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, role.Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + internal ChannelPermissions GetPermissions(User user) => _permissions.GetPermissions(user); + internal void UpdatePermissions() => _permissions.UpdatePermissions(); + internal void UpdatePermissions(User user) => _permissions.UpdatePermissions(user); + internal bool ResolvePermissions(User user, ref ChannelPermissions permissions) => _permissions.ResolvePermissions(user, ref permissions); + + internal override PermissionManager PermissionManager => null; + + /// Creates a new invite to this channel. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) + { + if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); + if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); + + var request = new CreateInviteRequest(Id) + { + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + IsTemporary = tempMembership, + WithXkcdPass = withXkcd + }; + + var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); + var invite = new Invite(response, Client); + return invite; + } + + internal void AddUser(User user) => _permissions.AddUser(user); + internal void RemoveUser(ulong id) => _permissions.RemoveUser(id); + + public override string ToString() => $"{Server}/{Name ?? Id.ToIdString()}"; + } +} diff --git a/src/Discord.Net/Models/Region.cs b/src/Discord.Net/Entities/Region.cs similarity index 89% rename from src/Discord.Net/Models/Region.cs rename to src/Discord.Net/Entities/Region.cs index 5b0354048..cf144a223 100644 --- a/src/Discord.Net/Models/Region.cs +++ b/src/Discord.Net/Entities/Region.cs @@ -16,5 +16,7 @@ Port = port; Vip = vip; } + + public override string ToString() => Name; } } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Entities/Role.cs similarity index 87% rename from src/Discord.Net/Models/Role.cs rename to src/Discord.Net/Entities/Role.cs index 1bdec0d3c..4a7955994 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Entities/Role.cs @@ -32,7 +32,7 @@ namespace Discord public ServerPermissions Permissions { get; private set; } /// Gets the color of this role. public Color Color { get; private set; } - + /// Gets true if this is the role representing all users in a server. public bool IsEveryone => Id == Server.Id; /// Gets a list of all members in this role. @@ -50,9 +50,9 @@ namespace Discord Color = new Color(0); } - internal void Update(APIRole model) + internal void Update(APIRole model, bool updatePermissions) { - if (model.Name != null) + if (model.Name != null) Name = model.Name; if (model.Hoist != null) IsHoisted = model.Hoist.Value; @@ -62,11 +62,15 @@ namespace Discord Position = model.Position.Value; if (model.Color != null) Color = new Color(model.Color.Value); - if (model.Permissions != null) - Permissions = new ServerPermissions(model.Permissions.Value); - - foreach (var member in Members) - Server.UpdatePermissions(member); + if (model.Permissions != null) + { + Permissions = new ServerPermissions(model.Permissions.Value); + if (updatePermissions) //Dont update these during READY + { + foreach (var member in Members) + Server.UpdatePermissions(member); + } + } } public async Task Edit(string name = null, ServerPermissions? permissions = null, Color color = null, bool? isHoisted = null, int? position = null) @@ -120,12 +124,11 @@ namespace Discord internal Role Clone() { - var result = new Role(); + var result = new Role(Id, Server); _cloner(this, result); return result; } - private Role() { } //Used for cloning - public override string ToString() => Name ?? Id.ToIdString(); + public override string ToString() => $"{Server}/{Name ?? Id.ToString()}"; } } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Entities/Server.cs similarity index 71% rename from src/Discord.Net/Models/Server.cs rename to src/Discord.Net/Entities/Server.cs index 6fb42ed92..a5e586126 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Entities/Server.cs @@ -1,4 +1,6 @@ -using Discord.API.Client; +using APIChannel = Discord.API.Client.Channel; +using APIMember = Discord.API.Client.Member; +using Discord.API.Client; using Discord.API.Client.Rest; using Discord.Net; using System; @@ -46,9 +48,9 @@ namespace Discord } } - private readonly ConcurrentDictionary _roles; - private readonly ConcurrentDictionary _users; - private readonly ConcurrentDictionary _channels; + private ConcurrentDictionary _roles; + private ConcurrentDictionary _users; + private ConcurrentDictionary _channels; private ulong _ownerId; private ulong? _afkChannelId; private int _userCount; @@ -57,34 +59,33 @@ namespace Discord /// Gets the unique identifier for this server. public ulong Id { get; } - /// Gets the default channel for this server. - public Channel DefaultChannel { get; } - /// Gets the the role representing all users in a server. - public Role EveryoneRole { get; } /// Gets the name of this server. - public string Name { get; private set; } + public string Name { get; set; } /// Gets the voice region for this server. - public Region Region { get; private set; } - /// Gets the unique identifier for this user's current avatar. - public string IconId { get; private set; } - /// Gets the unique identifier for this server's custom splash image. - public string SplashId { get; private set; } + public Region Region { get; set; } + /// Gets the AFK voice channel for this server. + public VoiceChannel AFKChannel { get; set; } /// Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. - public int AFKTimeout { get; private set; } + public int AFKTimeout { get; set; } + /// Gets the date and time you joined this server. public DateTime JoinedAt { get; private set; } + /// Gets the the role representing all users in a server. + public Role EveryoneRole { get; private set; } /// Gets all extra features added to this server. public IEnumerable Features { get; private set; } /// Gets all custom emojis on this server. public IEnumerable CustomEmojis { get; private set; } - + /// Gets the unique identifier for this user's current avatar. + public string IconId { get; private set; } + /// Gets the unique identifier for this server's custom splash image. + public string SplashId { get; private set; } + /// Gets the user that created this server. public User Owner => GetUser(_ownerId); - /// Returns true if the current user owns this server. - public bool IsOwner => _ownerId == Client.CurrentUser.Id; - /// Gets the AFK voice channel for this server. - public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; + /// Gets the default channel for this server. + public TextChannel DefaultChannel => _channels[Id] as TextChannel; /// Gets the current user in this server. public User CurrentUser => GetUser(Client.CurrentUser.Id); /// Gets the URL to this server's current icon. @@ -93,11 +94,11 @@ namespace Discord public string SplashUrl => GetSplashUrl(Id, SplashId); /// Gets a collection of all channels in this server. - public IEnumerable AllChannels => _channels.Select(x => x.Value); + public IEnumerable AllChannels => _channels.Select(x => x.Value); /// Gets a collection of text channels in this server. - public IEnumerable TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); + public IEnumerable TextChannels => _channels.Where(x => x.Value.IsText).Select(x => x.Value as TextChannel); /// Gets a collection of voice channels in this server. - public IEnumerable VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); + public IEnumerable VoiceChannels => _channels.Where(x => x.Value.IsVoice).Select(x => x.Value as VoiceChannel); /// Gets a collection of all members in this server. public IEnumerable Users => _users.Select(x => x.Value.User); /// Gets a collection of all roles in this server. @@ -105,32 +106,23 @@ namespace Discord /// Gets the number of channels in this server. public int ChannelCount => _channels.Count; + /// Gets the number of users downloaded for this server so far. + internal int CurrentUserCount => _users.Count; /// Gets the number of users in this server. public int UserCount => _userCount; /// Gets the number of roles in this server. - public int RoleCount => _channels.Count; + public int RoleCount => _roles.Count; - internal Server(DiscordClient client, ulong id) + internal Server(ulong id, DiscordClient client) { - Client = client; Id = id; - - _channels = new ConcurrentDictionary(); - _roles = new ConcurrentDictionary(); - _users = new ConcurrentDictionary(); - DefaultChannel = AddChannel(id); - EveryoneRole = AddRole(id); + Client = client; } - internal void Update(GuildReference model) + internal void Update(Guild model) { if (model.Name != null) Name = model.Name; - } - internal void Update(Guild model) - { - Update(model as GuildReference); - if (model.AFKTimeout != null) AFKTimeout = model.AFKTimeout.Value; if (model.JoinedAt != null) @@ -143,7 +135,17 @@ namespace Discord IconId = model.Icon; if (model.Features != null) Features = model.Features; - if (model.Emojis != null) + if (model.Roles != null) + { + _roles = new ConcurrentDictionary(2, model.Roles.Length); + foreach (var x in model.Roles) + { + var role = AddRole(x.Id); + role.Update(x, false); + } + EveryoneRole = _roles[Id]; + } + if (model.Emojis != null) //Needs Roles { CustomEmojis = model.Emojis.Select(x => new Emoji(x.Id) { @@ -153,11 +155,6 @@ namespace Discord Roles = x.RoleIds.Select(y => GetRole(y)).Where(y => y != null).ToArray() }).ToArray(); } - if (model.Roles != null) - { - foreach (var x in model.Roles) - AddRole(x.Id).Update(x); - } //Can be null _afkChannelId = model.AFKChannelId; @@ -167,28 +164,37 @@ namespace Discord { Update(model as Guild); + //Only channels or members should have AddXXX(cachePerms: true), not both if (model.Channels != null) { + _channels = new ConcurrentDictionary(2, (int)(model.Channels.Length * 1.05)); foreach (var subModel in model.Channels) - AddChannel(subModel.Id).Update(subModel); - } - if (model.Members != null) - { - foreach (var subModel in model.Members) - AddUser(subModel.User.Id).Update(subModel); + AddChannel(subModel, false); } - if (model.VoiceStates != null) + if (model.MemberCount != null) { - foreach (var subModel in model.VoiceStates) - GetUser(subModel.UserId)?.Update(subModel); + if (_users == null) + _users = new ConcurrentDictionary(2, (int)(model.MemberCount * 1.05)); + _userCount = model.MemberCount.Value; } - if (model.Presences != null) + if (!model.IsLarge) { - foreach (var subModel in model.Presences) - GetUser(subModel.User.Id)?.Update(subModel); + if (model.Members != null) + { + foreach (var subModel in model.Members) + AddUser(subModel, true, false).Update(subModel); + } + if (model.VoiceStates != null) + { + foreach (var subModel in model.VoiceStates) + GetUser(subModel.UserId)?.Update(subModel); + } + if (model.Presences != null) + { + foreach (var subModel in model.Presences) + GetUser(subModel.User.Id)?.Update(subModel); + } } - if (model.MemberCount != null) - _userCount = model.MemberCount.Value; } /// Edits this server, changing only non-null attributes. @@ -209,17 +215,13 @@ namespace Discord /// Leaves this server. This function will fail if you're the owner - use Delete instead. public async Task Leave() { - if (_ownerId == CurrentUser.Id) - throw new InvalidOperationException("Unable to leave a server you own, use Server.Delete instead"); try { await Client.ClientAPI.Send(new LeaveGuildRequest(Id)).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } /// Deletes this server. This function will fail if you're not the owner - use Leave instead. public async Task Delete() { - if (_ownerId != CurrentUser.Id) - throw new InvalidOperationException("Unable to delete a server you don't own, use Server.Leave instead"); - try { await Client.ClientAPI.Send(new LeaveGuildRequest(Id)).ConfigureAwait(false); } + try { await Client.ClientAPI.Send(new DeleteGuildRequest(Id)).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -229,8 +231,7 @@ namespace Discord var response = await Client.ClientAPI.Send(new GetBansRequest(Id)).ConfigureAwait(false); return response.Select(x => { - var user = new User(Client, x.Id, this); - user.Update(x); + var user = new User(x, Client, this); return user; }); } @@ -253,72 +254,63 @@ namespace Discord #endregion #region Channels - internal Channel AddChannel(ulong id) + internal PublicChannel AddChannel(APIChannel model, bool cachePerms) { - var channel = new Channel(Client, id, this); + PublicChannel channel; + ChannelType type = EnumConverters.ToChannelType(model.Type); + if (type == ChannelType.Voice) + channel = new VoiceChannel(model, this); + else + channel = new TextChannel(model, this); + + if (cachePerms && Client.Config.UsePermissionsCache) + { + foreach (var user in Users) + channel.AddUser(user); + } Client.AddChannel(channel); - return _channels.GetOrAdd(id, x => channel); + return _channels.GetOrAdd(model.Id, x => channel); } - internal Channel RemoveChannel(ulong id) + internal PublicChannel RemoveChannel(ulong id) { - Channel channel; + PublicChannel channel; _channels.TryRemove(id, out channel); return channel; } /// Gets the channel with the provided id and owned by this server, or null if not found. - public Channel GetChannel(ulong id) + public PublicChannel GetChannel(ulong id) { - Channel result; + PublicChannel result; _channels.TryGetValue(id, out result); return result; } - - /// Returns all channels with the specified server and name. - /// Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable FindChannels(string name, ChannelType type = null, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - return _channels.Select(x => x.Value).Find(name, type, exactMatch); - } + public TextChannel GetTextChannel(ulong id) => GetChannel(id) as TextChannel; + public VoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as VoiceChannel; /// Creates a new channel. - public async Task CreateChannel(string name, ChannelType type) + public async Task CreateChannel(string name, ChannelType type) { if (name == null) throw new ArgumentNullException(nameof(name)); - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type != ChannelType.Text && type != ChannelType.Voice) throw new ArgumentException("Invalid channel type", nameof(type)); - var request = new CreateChannelRequest(Id) { Name = name, Type = type.Value }; + var request = new CreateChannelRequest(Id) { Name = name, Type = type }; var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); - var channel = AddChannel(response.Id); + var channel = AddChannel(response, true); channel.Update(response); return channel; } - - /// Reorders the provided channels and optionally places them after a certain channel. - public Task ReorderChannels(IEnumerable channels, Channel after = null) - { - if (channels == null) throw new ArgumentNullException(nameof(channels)); - - var request = new ReorderChannelsRequest(Id) - { - ChannelIds = channels.Select(x => x.Id).ToArray(), - StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position) - }; - return Client.ClientAPI.Send(request); - } #endregion #region Invites /// Gets all active (non-expired) invites to this server. - public async Task> GetInvites() + public async Task> DownloadInvites() { var response = await Client.ClientAPI.Send(new GetInvitesRequest(Id)).ConfigureAwait(false); return response.Select(x => { - var invite = new Invite(Client, x.Code, x.XkcdPass); + var invite = new Invite(x, Client); invite.Update(x); return invite; }); @@ -350,13 +342,6 @@ namespace Discord _roles.TryGetValue(id, out result); return result; } - /// Returns all roles with the specified server and name. - /// Search is case-insensitive if exactMatch is false. - public IEnumerable FindRoles(string name, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return _roles.Select(x => x.Value).Find(name, exactMatch); - } /// Creates a new role. public async Task CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) @@ -366,7 +351,7 @@ namespace Discord var createRequest = new CreateRoleRequest(Id); var createResponse = await Client.ClientAPI.Send(createRequest).ConfigureAwait(false); var role = AddRole(createResponse.Id); - role.Update(createResponse); + role.Update(createResponse, false); var editRequest = new UpdateRoleRequest(role.Server.Id, role.Id) { @@ -376,7 +361,7 @@ namespace Discord IsHoisted = isHoisted }; var editResponse = await Client.ClientAPI.Send(editRequest).ConfigureAwait(false); - role.Update(editResponse); + role.Update(editResponse, true); return role; } @@ -444,20 +429,27 @@ namespace Discord #endregion #region Users - internal User AddUser(ulong id) + internal User AddUser(APIMember model, bool cachePerms, bool incrementCount) { - _userCount++; - Member member = new Member(new User(Client, id, this), ServerPermissions.None); - if (id == Client.CurrentUser.Id) - { - member.User.CurrentGame = Client.CurrentGame; - member.User.Status = Client.Status; - } - - if (_users.TryGetOrAdd(id, member, out member)) + if (incrementCount) + _userCount++; + + Member member; + if (!_users.TryGetValue(model.User.Id, out member)) //Users can only be added from websocket thread, ignore threadsafety { - foreach (var channel in AllChannels) - channel.AddUser(member.User); + member = new Member(new User(model, Client, this), ServerPermissions.None); + if (model.User.Id == Client.CurrentUser.Id) + { + member.User.CurrentGame = Client.CurrentGame; + member.User.Status = Client.Status; + } + + _users[model.User.Id] = member; + if (cachePerms && Client.Config.UsePermissionsCache) + { + foreach (var channel in _channels) + channel.Value.AddUser(member.User); + } } return member.User; } @@ -467,8 +459,8 @@ namespace Discord Member member; if (_users.TryRemove(id, out member)) { - foreach (var channel in AllChannels) - channel.RemoveUser(id); + foreach (var channel in _channels) + channel.Value.RemoveUser(id); return member.User; } return null; @@ -488,15 +480,7 @@ namespace Discord { if (name == null) throw new ArgumentNullException(nameof(name)); - return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault(); - } - /// Returns all members of this server with the specified name. - /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable FindUsers(string name, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch); + return _users.Select(x => x.Value.User).Where(x => x.Discriminator == discriminator && x.Name == name).SingleOrDefault(); } /// Kicks all users with an inactivity greater or equal to the provided number of days. @@ -517,11 +501,10 @@ namespace Discord internal Server Clone() { - var result = new Server(); + var result = new Server(Id, Client); _cloner(this, result); return result; } - private Server() { } //Used for cloning public override string ToString() => Name ?? Id.ToIdString(); } diff --git a/src/Discord.Net/Entities/TextChannel.cs b/src/Discord.Net/Entities/TextChannel.cs new file mode 100644 index 000000000..8ee97aad1 --- /dev/null +++ b/src/Discord.Net/Entities/TextChannel.cs @@ -0,0 +1,88 @@ +using Discord.API.Client.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using APIChannel = Discord.API.Client.Channel; + +namespace Discord +{ + public class TextChannel : PublicChannel, IPublicChannel, ITextChannel + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + private readonly MessageManager _messages; + + /// Gets or sets the topic of this channel. + public string Topic { get; set; } + + public override ChannelType Type => ChannelType.Text; + /// Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. + public IEnumerable Messages => _messages != null ? _messages : Enumerable.Empty(); + /// Gets a collection of all users with read access to this channel. + public override IEnumerable Users + { + get + { + if (Client.Config.UsePermissionsCache) + return _permissions.Users.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User); + else + { + ChannelPermissions perms = new ChannelPermissions(); + return Server.Users.Where(x => + { + _permissions.ResolvePermissions(x, ref perms); + return perms.ReadMessages == true; + }); + } + } + } + + internal override MessageManager MessageManager => _messages; + + internal TextChannel(APIChannel model, Server server) + : base(model, server) + { + if (Client.Config.MessageCacheSize > 0) + _messages = new MessageManager(this, (int)(Client.Config.MessageCacheSize * 1.05)); + } + private TextChannel(ulong id, Server server) + : base(id, server) + { + } + + internal override void Update(APIChannel model) + { + base.Update(model); + if (model.Topic != null) Topic = model.Topic; + } + /// Save all changes to this channel. + public override async Task Save() + { + var request = new UpdateChannelRequest(Id) + { + Name = Name, + Topic = Topic, + Position = Position + }; + await Client.ClientAPI.Send(request).ConfigureAwait(false); + } + + public Message GetMessage(ulong id) => _messages.Get(id); + public Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) + => _messages.Download(limit, relativeMessageId, relativeDir); + + public Task SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); + public Task SendFile(string filePath) => _messages.SendFile(filePath); + public Task SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream); + public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); + + internal override Channel Clone() + { + var result = new TextChannel(Id, Server); + _cloner(this, result); + return result; + } + } +} diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Entities/User.cs similarity index 77% rename from src/Discord.Net/Models/User.cs rename to src/Discord.Net/Entities/User.cs index b2f5488cf..89a0b1133 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Entities/User.cs @@ -19,12 +19,12 @@ namespace Discord [Flags] private enum VoiceState : byte { - None = 0x0, + Normal = 0x0, SelfMuted = 0x01, SelfDeafened = 0x02, - ServerMuted = 0x04, - ServerDeafened = 0x08, - ServerSuppressed = 0x10, + ServerMuted = 0x10, + ServerDeafened = 0x20, + ServerSuppressed = 0x40, } internal struct CompositeKey : IEquatable @@ -74,9 +74,10 @@ namespace Discord // public string Token { get; private set; } /// Gets the current private channel for this user if one exists. - public Channel PrivateChannel => Client.GetPrivateChannel(Id); + public PrivateChannel PrivateChannel => Client.GetPrivateChannel(Id); /// Returns the string used to mention this user. public string Mention => $"<@{Id}>"; + public bool IsOwner => Server == null ? false : this == Server.Owner; /// Returns true if this user has marked themselves as muted. public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; /// Returns true if this user has marked themselves as deafened. @@ -90,14 +91,15 @@ namespace Discord /// Returns the time this user was last seen online in this server. public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; /// Gets this user's current voice channel. - public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null; + public VoiceChannel VoiceChannel => _voiceChannelId != null ? Server.GetVoiceChannel(_voiceChannelId.Value) : null; /// Gets the URL to this user's current avatar. public string AvatarUrl => GetAvatarUrl(Id, AvatarId); /// Gets all roles that have been assigned to this user, including the everyone role. public IEnumerable Roles => _roles.Select(x => x.Value); + public ServerPermissions ServerPermissions => Server.GetPermissions(this); /// Returns a collection of all channels this user has permissions to join on this server. - public IEnumerable Channels + public IEnumerable Channels { get { @@ -106,8 +108,8 @@ namespace Discord if (Client.Config.UsePermissionsCache) { return Server.AllChannels.Where(x => - (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) || - (x.Type == ChannelType.Voice && x.GetPermissions(this).Connect)); + (x.IsText && x.GetPermissions(this).ReadMessages) || + (x.IsVoice && x.GetPermissions(this).Connect)); } else { @@ -115,7 +117,7 @@ namespace Discord return Server.AllChannels .Where(x => { - x.UpdatePermissions(this, ref perms); + x.ResolvePermissions(this, ref perms); return (x.Type == ChannelType.Text && perms.ReadMessages) || (x.Type == ChannelType.Voice && perms.Connect); }); @@ -129,26 +131,50 @@ namespace Discord { var privateChannel = Client.GetPrivateChannel(Id); if (privateChannel != null) - return new Channel[] { privateChannel }; + return new IChannel[] { privateChannel }; else - return new Channel[0]; + return new IChannel[0]; } } } } - internal User(DiscordClient client, ulong id, Server server) - { + internal User(ExtendedMember model, DiscordClient client, Server server) + : this(model as APIMember, client, server) + { + if (model.IsServerMuted == true) + _voiceState |= VoiceState.ServerMuted; + else if (model.IsServerMuted == false) + _voiceState &= ~VoiceState.ServerMuted; + + if (model.IsServerDeafened == true) + _voiceState |= VoiceState.ServerDeafened; + else if (model.IsServerDeafened == false) + _voiceState &= ~VoiceState.ServerDeafened; + } + internal User(APIMember model, DiscordClient client, Server server) + : this(model.User.Id, client, server) + { + if (server == null) + UpdateRoles(null); + Update(model); + } + internal User(UserReference model, DiscordClient client, Server server) + : this(model.Id, client, server) + { + if (server == null) + UpdateRoles(null); + Update(model); + } + private User(ulong id, DiscordClient client, Server server) + { Client = client; Id = id; Server = server; - _roles = new Dictionary(); - Status = UserStatus.Offline; - - if (server == null) - UpdateRoles(null); - } + _roles = new Dictionary(); + Status = UserStatus.Offline; + } internal void Update(UserReference model) { @@ -168,20 +194,6 @@ namespace Discord JoinedAt = model.JoinedAt.Value; if (model.Roles != null) UpdateRoles(model.Roles.Select(x => Server.GetRole(x))); - } - internal void Update(ExtendedMember model) - { - Update(model as APIMember); - - if (model.IsServerMuted == true) - _voiceState |= VoiceState.ServerMuted; - else if (model.IsServerMuted == false) - _voiceState &= ~VoiceState.ServerMuted; - - if (model.IsServerDeafened == true) - _voiceState |= VoiceState.ServerDeafened; - else if (model.IsServerDeafened == false) - _voiceState &= ~VoiceState.ServerDeafened; } internal void Update(MemberPresence model) { @@ -264,56 +276,31 @@ namespace Discord var request = new KickMemberRequest(Server.Id, Id); return Client.ClientAPI.Send(request); } - - #region Permissions - public ServerPermissions ServerPermissions => Server.GetPermissions(this); - public ChannelPermissions GetPermissions(Channel channel) + + public ChannelPermissions GetPermissions(PublicChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); return channel.GetPermissions(this); } - #endregion - - #region Channels - public Task CreatePMChannel() - => Client.CreatePMChannel(this); - #endregion - - #region Messages - public async Task SendMessage(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - - var channel = await CreatePMChannel().ConfigureAwait(false); - return await channel.SendMessage(text).ConfigureAwait(false); - } - public async Task SendFile(string filePath) - { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - - var channel = await CreatePMChannel().ConfigureAwait(false); - return await channel.SendFile(filePath).ConfigureAwait(false); - } - public async Task SendFile(string filename, Stream stream) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - if (stream == null) throw new ArgumentNullException(nameof(stream)); - - var channel = await CreatePMChannel().ConfigureAwait(false); - return await channel.SendFile(filename, stream).ConfigureAwait(false); - } - #endregion - - #region Roles + + public Task CreatePMChannel() => Client.CreatePrivateChannel(this); + private void UpdateRoles(IEnumerable roles) { + bool updated = false; var newRoles = new Dictionary(); + + var oldRoles = _roles; if (roles != null) { foreach (var r in roles) { if (r != null) + { newRoles[r.Id] = r; + if (!oldRoles.ContainsKey(r.Id)) + updated = true; //Check for adds + } } } @@ -322,10 +309,15 @@ namespace Discord var everyone = Server.EveryoneRole; newRoles[everyone.Id] = everyone; } - _roles = newRoles; + if (oldRoles.Count != newRoles.Count) + updated = true; //Check for removes - if (Server != null) - Server.UpdatePermissions(this); + if (updated) + { + _roles = newRoles; + if (Server != null) + Server.UpdatePermissions(this); + } } public bool HasRole(Role role) { @@ -334,20 +326,31 @@ namespace Discord return _roles.ContainsKey(role.Id); } - public Task AddRoles(params Role[] roles) - => Edit(roles: Roles.Concat(roles)); - public Task RemoveRoles(params Role[] roles) - => Edit(roles: Roles.Except(roles)); - #endregion + public Task AddRoles(params Role[] roles) => Edit(roles: Roles.Concat(roles)); + public Task AddRoles(IEnumerable roles) => Edit(roles: Roles.Concat(roles)); + public Task RemoveRoles(params Role[] roles) => Edit(roles: Roles.Except(roles)); + public Task RemoveRoles(IEnumerable roles) => Edit(roles: Roles.Except(roles)); internal User Clone() { - var result = new User(); + var result = new User(Id, Client, Server); _cloner(this, result); return result; } - private User() { } //Used for cloning - public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); - } + public override string ToString() + { + if (Name != null) + return $"{Server?.Name ?? "[Private]"}/{Name}#{Discriminator}"; + else + return $"{Server?.Name ?? "[Private]"}/{Id}"; + } + internal string ToString(IChannel channel) + { + if (Name != null) + return $"{channel}/{Name}#{Discriminator}"; + else + return $"{channel}/{Id}"; + } + } } \ No newline at end of file diff --git a/src/Discord.Net/Entities/VoiceChannel.cs b/src/Discord.Net/Entities/VoiceChannel.cs new file mode 100644 index 000000000..5b40a6aad --- /dev/null +++ b/src/Discord.Net/Entities/VoiceChannel.cs @@ -0,0 +1,60 @@ +using APIChannel = Discord.API.Client.Channel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord.API.Client.Rest; + +namespace Discord +{ + public class VoiceChannel : PublicChannel, IPublicChannel, IVoiceChannel + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + public int Bitrate { get; set; } + + public override ChannelType Type => ChannelType.Public | ChannelType.Voice; + /// Gets a collection of all users currently in this voice channel. + public override IEnumerable Users + { + get + { + if (Client.Config.UsePermissionsCache) + return _permissions.Users.Select(x => x.User).Where(x => x.VoiceChannel == this); + else + return Server.Users.Where(x => x.VoiceChannel == this); + } + } + + internal override MessageManager MessageManager => null; + + internal VoiceChannel(APIChannel model, Server server) + : base(model, server) + { + } + private VoiceChannel(ulong id, Server server) + : base(id, server) + { + } + + + /// Save all changes to this channel. + public override async Task Save() + { + var request = new UpdateChannelRequest(Id) + { + Name = Name, + Position = Position, + Bitrate = Bitrate + }; + await Client.ClientAPI.Send(request).ConfigureAwait(false); + } + + internal override Channel Clone() + { + var result = new VoiceChannel(Id, Server); + _cloner(this, result); + return result; + } + } +} diff --git a/src/Discord.Net/EnumConverters.cs b/src/Discord.Net/EnumConverters.cs new file mode 100644 index 000000000..7448055f6 --- /dev/null +++ b/src/Discord.Net/EnumConverters.cs @@ -0,0 +1,42 @@ +using System; + +namespace Discord +{ + public static class EnumConverters + { + public static ChannelType ToChannelType(string value) + { + switch (value) + { + case "text": return ChannelType.Text; + case "voice": return ChannelType.Voice; + default: throw new ArgumentException("Unknown channel type", nameof(value)); + } + } + public static string ToString(ChannelType value) + { + if ((value & ChannelType.Text) != 0) return "text"; + if ((value & ChannelType.Voice) != 0) return "voice"; + throw new ArgumentException("Invalid channel tType", nameof(value)); + } + + public static PermissionTarget ToPermissionTarget(string value) + { + switch (value) + { + case "member": return PermissionTarget.User; + case "role": return PermissionTarget.Role; + default: throw new ArgumentException("Unknown permission target", nameof(value)); + } + } + public static string ToString(PermissionTarget value) + { + switch (value) + { + case PermissionTarget.User: return "member"; + case PermissionTarget.Role: return "role"; + default: throw new ArgumentException("Invalid permission target", nameof(value)); + } + } + } +} diff --git a/src/Discord.Net/Enums/ChannelType.cs b/src/Discord.Net/Enums/ChannelType.cs index 9ed49a701..77b0614ea 100644 --- a/src/Discord.Net/Enums/ChannelType.cs +++ b/src/Discord.Net/Enums/ChannelType.cs @@ -2,36 +2,12 @@ namespace Discord { - public class ChannelType : StringEnum, IEquatable - { - /// A text-only channel. - public static ChannelType Text { get; } = new ChannelType("text"); - /// A voice-only channel. - public static ChannelType Voice { get; } = new ChannelType("voice"); - - private ChannelType(string value) - : base(value) { } - - public static ChannelType FromString(string value) - { - switch (value) - { - case null: - return null; - case "text": - return Text; - case "voice": - return Voice; - default: - return new ChannelType(value); - } - } - - public static implicit operator ChannelType(string value) => FromString(value); - public static bool operator ==(ChannelType a, ChannelType b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); - public static bool operator !=(ChannelType a, ChannelType b) => !(a == b); - public override int GetHashCode() => Value.GetHashCode(); - public override bool Equals(object obj) => (obj as ChannelType)?.Equals(this) ?? false; - public bool Equals(ChannelType type) => type != null && type.Value == Value; + [Flags] + public enum ChannelType : byte + { + Public = 0x01, + Private = 0x02, + Text = 0x10, + Voice = 0x20 } } diff --git a/src/Discord.Net/Enums/MessageState.cs b/src/Discord.Net/Enums/MessageState.cs new file mode 100644 index 000000000..59f65614d --- /dev/null +++ b/src/Discord.Net/Enums/MessageState.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + public enum MessageState : byte + { + /// Message did not originate from this session, or was successfully sent. + Normal = 0, + /// Message is current queued. + Queued, + /// Message was deleted. + Deleted, + /// Message was deleted before it was sent. + Aborted, + /// Message failed to be sent. + Failed, + /// Message has been removed from cache and will no longer receive updates. + Detached + } +} diff --git a/src/Discord.Net/Enums/PermissionTarget.cs b/src/Discord.Net/Enums/PermissionTarget.cs index 38a70e013..d1381a5ec 100644 --- a/src/Discord.Net/Enums/PermissionTarget.cs +++ b/src/Discord.Net/Enums/PermissionTarget.cs @@ -1,35 +1,8 @@ namespace Discord { - public class PermissionTarget : StringEnum + public enum PermissionTarget : byte { - /// A text-only channel. - public static PermissionTarget Role { get; } = new PermissionTarget("role"); - /// A voice-only channel. - public static PermissionTarget User { get; } = new PermissionTarget("member"); - - private PermissionTarget(string value) - : base(value) { } - - public static PermissionTarget FromString(string value) - { - switch (value) - { - case null: - return null; - case "role": - return Role; - case "member": - return User; - default: - return new PermissionTarget(value); - } - } - - public static implicit operator PermissionTarget(string value) => FromString(value); - public static bool operator ==(PermissionTarget a, PermissionTarget b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); - public static bool operator !=(PermissionTarget a, PermissionTarget b) => !(a == b); - public override int GetHashCode() => Value.GetHashCode(); - public override bool Equals(object obj) => (obj as PermissionTarget)?.Equals(this) ?? false; - public bool Equals(PermissionTarget type) => type != null && type.Value == Value; + User, + Role } } diff --git a/src/Discord.Net/Events/ChannelEventArgs.cs b/src/Discord.Net/Events/ChannelEventArgs.cs index 4aecf34f1..ac31a27f5 100644 --- a/src/Discord.Net/Events/ChannelEventArgs.cs +++ b/src/Discord.Net/Events/ChannelEventArgs.cs @@ -4,10 +4,8 @@ namespace Discord { public class ChannelEventArgs : EventArgs { - public Channel Channel { get; } + public IChannel Channel { get; } - public Server Server => Channel.Server; - - public ChannelEventArgs(Channel channel) { Channel = channel; } + public ChannelEventArgs(IChannel channel) { Channel = channel; } } } diff --git a/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs index fa8da98ea..138f2dd8e 100644 --- a/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs +++ b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs @@ -4,12 +4,10 @@ namespace Discord { public class ChannelUpdatedEventArgs : EventArgs { - public Channel Before { get; } - public Channel After { get; } + public IChannel Before { get; } + public IChannel After { get; } - public Server Server => After.Server; - - public ChannelUpdatedEventArgs(Channel before, Channel after) + public ChannelUpdatedEventArgs(IChannel before, IChannel after) { Before = before; After = after; diff --git a/src/Discord.Net/Events/MessageEventArgs.cs b/src/Discord.Net/Events/MessageEventArgs.cs index 7edb23347..76c9455dc 100644 --- a/src/Discord.Net/Events/MessageEventArgs.cs +++ b/src/Discord.Net/Events/MessageEventArgs.cs @@ -7,7 +7,7 @@ namespace Discord public Message Message { get; } public User User => Message.User; - public Channel Channel => Message.Channel; + public ITextChannel Channel => Message.Channel; public Server Server => Message.Server; public MessageEventArgs(Message msg) { Message = msg; } diff --git a/src/Discord.Net/Events/MessageUpdatedEventArgs.cs b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs index 849f234e1..1583dc981 100644 --- a/src/Discord.Net/Events/MessageUpdatedEventArgs.cs +++ b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs @@ -8,7 +8,7 @@ namespace Discord public Message After { get; } public User User => After.User; - public Channel Channel => After.Channel; + public ITextChannel Channel => After.Channel; public Server Server => After.Server; public MessageUpdatedEventArgs(Message before, Message after) diff --git a/src/Discord.Net/Events/ChannelUserEventArgs.cs b/src/Discord.Net/Events/TypingEventArgs.cs similarity index 50% rename from src/Discord.Net/Events/ChannelUserEventArgs.cs rename to src/Discord.Net/Events/TypingEventArgs.cs index 819c7fcfa..73d47f688 100644 --- a/src/Discord.Net/Events/ChannelUserEventArgs.cs +++ b/src/Discord.Net/Events/TypingEventArgs.cs @@ -1,11 +1,11 @@ namespace Discord { - public class ChannelUserEventArgs + public class TypingEventArgs { - public Channel Channel { get; } + public ITextChannel Channel { get; } public User User { get; } - public ChannelUserEventArgs(Channel channel, User user) + public TypingEventArgs(ITextChannel channel, User user) { Channel = channel; User = user; diff --git a/src/Discord.Net/Extensions.cs b/src/Discord.Net/Extensions.cs deleted file mode 100644 index ee4d26e9a..000000000 --- a/src/Discord.Net/Extensions.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace Discord -{ - public static class DiscordClientExtensions - { - public static DiscordClient AddService(this DiscordClient client, T instance) - where T : class, IService - { - client.Services.Add(instance); - return client; - } - public static DiscordClient AddService(this DiscordClient client) - where T : class, IService, new() - { - client.Services.Add(new T()); - return client; - } - } - - internal static class InternalExtensions - { - internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture; - - public static ulong ToId(this string value) - => ulong.Parse(value, NumberStyles.None, _format); - public static ulong? ToNullableId(this string value) - => value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format); - public static bool TryToId(this string value, out ulong result) - => ulong.TryParse(value, NumberStyles.None, _format, out result); - - public static string ToIdString(this ulong value) - => value.ToString(_format); - public static string ToIdString(this ulong? value) - => value?.ToString(_format); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasBit(this uint rawValue, byte bit) => ((rawValue >> bit) & 1U) == 1; - - public static bool TryGetOrAdd(this ConcurrentDictionary d, - TKey key, Func factory, out TValue result) - { - bool created = false; - TValue newValue = default(TValue); - while (true) - { - if (d.TryGetValue(key, out result)) - return false; - if (!created) - { - newValue = factory(key); - created = true; - } - if (d.TryAdd(key, newValue)) - { - result = newValue; - return true; - } - } - } - public static bool TryGetOrAdd(this ConcurrentDictionary d, - TKey key, TValue value, out TValue result) - { - while (true) - { - if (d.TryGetValue(key, out result)) - return false; - if (d.TryAdd(key, value)) - { - result = value; - return true; - } - } - } - - public static IEnumerable Find(this IEnumerable channels, string name, ChannelType type = null, bool exactMatch = false) - { - //Search by name - var query = channels - .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - - if (!exactMatch) - { - if (name.Length >= 2 && name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Search by raw mention - { - ulong id; - if (name.Substring(2, name.Length - 3).TryToId(out id)) - { - var channel = channels.Where(x => x.Id == id).FirstOrDefault(); - if (channel != null) - query = query.Concat(new Channel[] { channel }); - } - } - if (name.Length >= 1 && name[0] == '#' && (type == null || type == ChannelType.Text)) //Search by clean mention - { - string name2 = name.Substring(1); - query = query.Concat(channels - .Where(x => x.Type == ChannelType.Text && string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); - } - } - - if (type != null) - query = query.Where(x => x.Type == type); - return query; - } - - public static IEnumerable Find(this IEnumerable users, - string name, ushort? discriminator = null, bool exactMatch = false) - { - //Search by name - var query = users - .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - - if (!exactMatch) - { - if (name.Length >= 2 && name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Search by raw mention - { - ulong id; - if (name.Substring(2, name.Length - 3).TryToId(out id)) - { - var user = users.Where(x => x.Id == id).FirstOrDefault(); - if (user != null) - query = query.Concat(new User[] { user }); - } - } - if (name.Length >= 1 && name[0] == '@') //Search by clean mention - { - string name2 = name.Substring(1); - query = query.Concat(users - .Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); - } - } - - if (discriminator != null) - query = query.Where(x => x.Discriminator == discriminator.Value); - return query; - } - - public static IEnumerable Find(this IEnumerable roles, string name, bool exactMatch = false) - { - // if (name.StartsWith("@")) - // { - // string name2 = name.Substring(1); - // return _roles.Where(x => x.Server.Id == server.Id && - // string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || - // string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - // } - // else - return roles.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - } - - public static IEnumerable Find(this IEnumerable servers, string name, bool exactMatch = false) - => servers.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - - public static string Base64(this Stream stream, ImageType type, string existingId) - { - if (type == ImageType.None) - return null; - else if (stream != null) - { - byte[] bytes = new byte[stream.Length - stream.Position]; - stream.Read(bytes, 0, bytes.Length); - - string base64 = Convert.ToBase64String(bytes); - string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; - return $"data:{imageType},{base64}"; - } - return existingId; - } - } -} diff --git a/src/Discord.Net/IModel.cs b/src/Discord.Net/IModel.cs new file mode 100644 index 000000000..8a86ceb3d --- /dev/null +++ b/src/Discord.Net/IModel.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public interface IModel + { + ulong Id { get; } + + Task Save(); + } +} diff --git a/src/Discord.Net/InternalExtensions.cs b/src/Discord.Net/InternalExtensions.cs new file mode 100644 index 000000000..3dec1c7bf --- /dev/null +++ b/src/Discord.Net/InternalExtensions.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Discord +{ + internal static class InternalExtensions + { + internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture; + + public static ulong ToId(this string value) + => ulong.Parse(value, NumberStyles.None, _format); + public static ulong? ToNullableId(this string value) + => value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format); + public static bool TryToId(this string value, out ulong result) + => ulong.TryParse(value, NumberStyles.None, _format, out result); + + public static string ToIdString(this ulong value) + => value.ToString(_format); + public static string ToIdString(this ulong? value) + => value?.ToString(_format); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasBit(this uint rawValue, byte bit) => ((rawValue >> bit) & 1U) == 1; + + public static bool TryGetOrAdd(this ConcurrentDictionary d, + TKey key, Func factory, out TValue result) + { + bool created = false; + TValue newValue = default(TValue); + while (true) + { + if (d.TryGetValue(key, out result)) + return false; + if (!created) + { + newValue = factory(key); + created = true; + } + if (d.TryAdd(key, newValue)) + { + result = newValue; + return true; + } + } + } + public static bool TryGetOrAdd(this ConcurrentDictionary d, + TKey key, TValue value, out TValue result) + { + while (true) + { + if (d.TryGetValue(key, out result)) + return false; + if (d.TryAdd(key, value)) + { + result = value; + return true; + } + } + } + + public static string Base64(this Stream stream, ImageType type, string existingId) + { + if (type == ImageType.None) + return null; + else if (stream != null) + { + byte[] bytes = new byte[stream.Length - stream.Position]; + stream.Read(bytes, 0, bytes.Length); + + string base64 = Convert.ToBase64String(bytes); + string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; + return $"data:{imageType},{base64}"; + } + return existingId; + } + } +} diff --git a/src/Discord.Net/Legacy.cs b/src/Discord.Net/Legacy.cs deleted file mode 100644 index be4cea7c0..000000000 --- a/src/Discord.Net/Legacy.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace Discord.Legacy -{ - public static class Mention - { - /// Returns the string used to create a user mention. - [Obsolete("Use User.Mention instead")] - public static string User(User user) - => user.Mention; - /// Returns the string used to create a channel mention. - [Obsolete("Use Channel.Mention instead")] - public static string Channel(Channel channel) - => channel.Mention; - /// Returns the string used to create a mention to everyone in a channel. - [Obsolete("Use Server.EveryoneRole.Mention instead")] - public static string Everyone() - => $"@everyone"; - } - - public static class LegacyExtensions - { - [Obsolete("Use DiscordClient.ExecuteAndWait")] - public static void Run(this DiscordClient client, Func asyncAction) - { - client.ExecuteAndWait(asyncAction); - } - [Obsolete("Use DiscordClient.Wait")] - public static void Run(this DiscordClient client) - { - client.Wait(); - } - - [Obsolete("Use Server.FindChannels")] - public static IEnumerable FindChannels(this DiscordClient client, Server server, string name, ChannelType type = null, bool exactMatch = false) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.FindChannels(name, type, exactMatch); - } - - [Obsolete("Use Server.CreateChannel")] - public static Task CreateChannel(this DiscordClient client, Server server, string name, ChannelType type) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.CreateChannel(name, type); - } - [Obsolete("Use User.CreateChannel")] - public static Task CreatePMChannel(this DiscordClient client, User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.CreatePMChannel(); - } - [Obsolete("Use Channel.Edit")] - public static Task EditChannel(this DiscordClient client, Channel channel, string name = null, string topic = null, int? position = null) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.Edit(name, topic, position); - } - [Obsolete("Use Channel.Delete")] - public static Task DeleteChannel(this DiscordClient client, Channel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.Delete(); - } - - [Obsolete("Use Server.ReorderChannels")] - public static Task ReorderChannels(this DiscordClient client, Server server, IEnumerable channels, Channel after = null) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.ReorderChannels(channels, after); - } - - [Obsolete("Use Server.GetInvites")] - public static Task> GetInvites(this DiscordClient client, Server server) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.GetInvites(); - } - - [Obsolete("Use Server.CreateInvite")] - public static Task CreateInvite(this DiscordClient client, Server server, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); - } - [Obsolete("Use Channel.CreateInvite")] - public static Task CreateInvite(this DiscordClient client, Channel channel, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); - } - - [Obsolete("Use Invite.Delete")] - public static Task DeleteInvite(this DiscordClient client, Invite invite) - { - if (invite == null) throw new ArgumentNullException(nameof(invite)); - return invite.Delete(); - } - [Obsolete("Use Invite.Accept")] - public static Task AcceptInvite(this DiscordClient client, Invite invite) - { - if (invite == null) throw new ArgumentNullException(nameof(invite)); - return invite.Accept(); - } - - [Obsolete("Use Channel.SendMessage")] - public static Task SendMessage(this DiscordClient client, Channel channel, string text) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.SendMessage(text); - } - [Obsolete("Use Channel.SendTTSMessage")] - public static Task SendTTSMessage(this DiscordClient client, Channel channel, string text) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.SendTTSMessage(text); - } - [Obsolete("Use Channel.SendFile")] - public static Task SendFile(this DiscordClient client, Channel channel, string filePath) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.SendFile(filePath); - } - [Obsolete("Use Channel.SendFile")] - public static Task SendFile(this DiscordClient client, Channel channel, string filename, Stream stream) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.SendFile(filename, stream); - } - [Obsolete("Use User.SendMessage")] - public static Task SendMessage(this DiscordClient client, User user, string text) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.SendMessage(text); - } - [Obsolete("Use User.SendFile")] - public static Task SendFile(this DiscordClient client, User user, string filePath) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.SendFile(filePath); - } - [Obsolete("Use User.SendFile")] - public static Task SendFile(this DiscordClient client, User user, string filename, Stream stream) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.SendFile(filename, stream); - } - - [Obsolete("Use Message.Edit")] - public static Task EditMessage(this DiscordClient client, Message message, string text) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - return message.Edit(text); - } - - [Obsolete("Use Message.Delete")] - public static Task DeleteMessage(this DiscordClient client, Message message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - return message.Delete(); - } - [Obsolete("Use Message.Delete")] - public static async Task DeleteMessages(this DiscordClient client, IEnumerable messages) - { - if (messages == null) throw new ArgumentNullException(nameof(messages)); - - foreach (var message in messages) - await message.Delete().ConfigureAwait(false); - } - - [Obsolete("Use Channel.DownloadMessages")] - public static Task DownloadMessages(this DiscordClient client, Channel channel, int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before, bool useCache = true) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.DownloadMessages(limit, relativeMessageId, relativeDir, useCache); - } - - [Obsolete("Use Server.GetUser")] - public static User GetUser(this DiscordClient client, Server server, ulong userId) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.GetUser(userId); - } - [Obsolete("Use Server.GetUser")] - public static User GetUser(this DiscordClient client, Server server, string username, ushort discriminator) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.GetUser(username, discriminator); - } - - [Obsolete("Use Server.FindUsers")] - public static IEnumerable FindUsers(this DiscordClient client, Server server, string name, bool exactMatch = false) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.FindUsers(name, exactMatch); - } - [Obsolete("Use Channel.FindUsers")] - public static IEnumerable FindUsers(this DiscordClient client, Channel channel, string name, bool exactMatch = false) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.FindUsers(name, exactMatch); - } - - [Obsolete("Use User.Edit")] - public static Task EditUser(this DiscordClient client, User user, bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable roles = null) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Edit(isMuted, isDeafened, voiceChannel, roles); - } - - [Obsolete("Use User.Kick")] - public static Task KickUser(this DiscordClient client, User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Kick(); - } - [Obsolete("Use Server.Ban")] - public static Task BanUser(this DiscordClient client, User user, int pruneDays = 0) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - var server = user.Server; - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.Ban(user, pruneDays); - } - [Obsolete("Use Server.Unban")] - public static Task UnbanUser(this DiscordClient client, User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - var server = user.Server; - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.Unban(user); - } - [Obsolete("Use Server.Unban")] - public static Task UnbanUser(this DiscordClient client, Server server, ulong userId) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.Unban(userId); - } - - [Obsolete("Use Server.PruneUsers")] - public static Task PruneUsers(this DiscordClient client, Server server, int days = 30, bool simulate = false) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.PruneUsers(days, simulate); - } - - [Obsolete("Use DiscordClient.CurrentUser.Edit")] - public static Task EditProfile(this DiscordClient client, string currentPassword = "", - string username = null, string email = null, string password = null, - Stream avatar = null, ImageType avatarType = ImageType.Png) - => client.CurrentUser.Edit(currentPassword, username, email, password, avatar, avatarType); - - [Obsolete("Use Server.GetRole")] - public static Role GetRole(this DiscordClient client, Server server, ulong id) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.GetRole(id); - } - [Obsolete("Use Server.FindRoles")] - public static IEnumerable FindRoles(this DiscordClient client, Server server, string name) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.FindRoles(name); - } - - [Obsolete("Use Server.CreateRole")] - public static Task CreateRole(this DiscordClient client, Server server, string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.CreateRole(name, permissions, color); - } - [Obsolete("Use Role.Edit")] - public static Task EditRole(this DiscordClient client, Role role, string name = null, ServerPermissions? permissions = null, Color color = null, bool? isHoisted = null, int? position = null) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - return role.Edit(name, permissions, color, isHoisted, position); - } - - [Obsolete("Use Role.Delete")] - public static Task DeleteRole(this DiscordClient client, Role role) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - return role.Delete(); - } - - [Obsolete("Use Server.ReorderRoles")] - public static Task ReorderRoles(this DiscordClient client, Server server, IEnumerable roles, Role after = null) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.ReorderRoles(roles, after); - } - - [Obsolete("Use Server.Edit")] - public static Task EditServer(this DiscordClient client, Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.Edit(name, region, icon, iconType); - } - - [Obsolete("Use Server.Leave")] - public static Task LeaveServer(this DiscordClient client, Server server) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - return server.Leave(); - } - - [Obsolete("Use DiscordClient.Regions")] - public static IEnumerable GetVoiceRegions(this DiscordClient client) - => client.Regions; - - [Obsolete("Use Channel.GetPermissionRule")] - public static ChannelPermissionOverrides GetChannelPermissions(this DiscordClient client, Channel channel, User user) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.GetPermissionsRule(user); - } - [Obsolete("Use Channel.GetPermissionRule")] - public static ChannelPermissionOverrides GetChannelPermissions(this DiscordClient client, Channel channel, Role role) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.GetPermissionsRule(role); - } - [Obsolete("Use Channel.AddPermissionRule(DualChannelPermissions)", true)] - public static Task SetChannelPermissions(this DiscordClient client, Channel channel, User user, ChannelPermissions allow, ChannelPermissions deny) - { - throw new InvalidOperationException(); - } - [Obsolete("Use Channel.AddPermissionRule")] - public static Task SetChannelPermissions(this DiscordClient client, Channel channel, User user, ChannelPermissionOverrides permissions) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.AddPermissionsRule(user, permissions); - } - [Obsolete("Use Channel.AddPermissionRule(DualChannelPermissions)")] - public static Task SetChannelPermissions(this DiscordClient client, Channel channel, Role role, ChannelPermissions allow, ChannelPermissions deny) - { - throw new InvalidOperationException(); - } - [Obsolete("Use Channel.AddPermissionRule")] - public static Task SetChannelPermissions(this DiscordClient client, Channel channel, Role role, ChannelPermissionOverrides permissions) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.AddPermissionsRule(role, permissions); - } - [Obsolete("Use Channel.RemovePermissionRule")] - public static Task RemoveChannelPermissions(this DiscordClient client, Channel channel, User user) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.RemovePermissionsRule(user); - } - [Obsolete("Use Channel.RemovePermissionRule")] - public static Task RemoveChannelPermissions(this DiscordClient client, Channel channel, Role role) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return channel.RemovePermissionsRule(role); - } - - [Obsolete("Removed", true)] - public static Task AckMessage(this DiscordClient client, Message message) - { - throw new InvalidOperationException(); - } - [Obsolete("Use Channel.ImportMessages", true)] - public static IEnumerable ImportMessages(Channel channel, string json) - { - throw new InvalidOperationException(); - } - [Obsolete("Use Channel.ExportMessages", true)] - public static string ExportMessages(Channel channel) - { - throw new InvalidOperationException(); - } - } -} diff --git a/src/Discord.Net/Logging/LogManager.cs b/src/Discord.Net/Logging/LogManager.cs index 425053abe..2abc4f10d 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net/Logging/LogManager.cs @@ -16,69 +16,59 @@ namespace Discord.Logging Level = client.Config.LogLevel; } -#if DOTNET5_4 - public void Log(LogSeverity severity, string source, FormattableString message, Exception exception = null) + public void Log(LogSeverity severity, string source, string message, Exception exception = null) { if (severity <= Level) { - try { Message(this, new LogMessageEventArgs(severity, source, message.ToString(), exception)); } + try { Message(this, new LogMessageEventArgs(severity, source, message, exception)); } catch { } //We dont want to log on log errors } } -#endif - public void Log(LogSeverity severity, string source, string message, Exception exception = null) + +#if DOTNET5_4 + public void Log(LogSeverity severity, string source, FormattableString message, Exception exception = null) { if (severity <= Level) { - try { Message(this, new LogMessageEventArgs(severity, source, message, exception)); } + try { Message(this, new LogMessageEventArgs(severity, source, message.ToString(), exception)); } catch { } //We dont want to log on log errors } } +#endif public void Error(string source, string message, Exception ex = null) => Log(LogSeverity.Error, source, message, ex); -#if DOTNET5_4 - public void Error(string source, FormattableString message, Exception ex = null) - => Log(LogSeverity.Error, source, message, ex); -#endif public void Error(string source, Exception ex) => Log(LogSeverity.Error, source, (string)null, ex); - public void Warning(string source, string message, Exception ex = null) => Log(LogSeverity.Warning, source, message, ex); -#if DOTNET5_4 - public void Warning(string source, FormattableString message, Exception ex = null) - => Log(LogSeverity.Warning, source, message, ex); -#endif public void Warning(string source, Exception ex) => Log(LogSeverity.Warning, source, (string)null, ex); - public void Info(string source, string message, Exception ex = null) => Log(LogSeverity.Info, source, message, ex); -#if DOTNET5_4 - public void Info(string source, FormattableString message, Exception ex = null) - => Log(LogSeverity.Info, source, message, ex); -#endif public void Info(string source, Exception ex) => Log(LogSeverity.Info, source, (string)null, ex); - public void Verbose(string source, string message, Exception ex = null) => Log(LogSeverity.Verbose, source, message, ex); -#if DOTNET5_4 - public void Verbose(string source, FormattableString message, Exception ex = null) - => Log(LogSeverity.Verbose, source, message, ex); -#endif public void Verbose(string source, Exception ex) => Log(LogSeverity.Verbose, source, (string)null, ex); - public void Debug(string source, string message, Exception ex = null) => Log(LogSeverity.Debug, source, message, ex); + public void Debug(string source, Exception ex) + => Log(LogSeverity.Debug, source, (string)null, ex); + #if DOTNET5_4 + public void Error(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Error, source, message, ex); + public void Warning(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Warning, source, message, ex); + public void Info(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Info, source, message, ex); + public void Verbose(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Verbose, source, message, ex); public void Debug(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Debug, source, message, ex); #endif - public void Debug(string source, Exception ex) - => Log(LogSeverity.Debug, source, (string)null, ex); public Logger CreateLogger(string name) => new Logger(this, name); } diff --git a/src/Discord.Net/Logging/Logger.cs b/src/Discord.Net/Logging/Logger.cs index e3e917a24..59d591163 100644 --- a/src/Discord.Net/Logging/Logger.cs +++ b/src/Discord.Net/Logging/Logger.cs @@ -17,54 +17,40 @@ namespace Discord.Logging public void Log(LogSeverity severity, string message, Exception exception = null) => _manager.Log(severity, Name, message, exception); -#if DOTNET5_4 - public void Log(LogSeverity severity, FormattableString message, Exception exception = null) - => _manager.Log(severity, Name, message, exception); -#endif - public void Error(string message, Exception exception = null) => _manager.Error(Name, message, exception); -#if DOTNET5_4 - public void Error(FormattableString message, Exception exception = null) - => _manager.Error(Name, message, exception); -#endif public void Error(Exception exception) => _manager.Error(Name, exception); - public void Warning(string message, Exception exception = null) => _manager.Warning(Name, message, exception); -#if DOTNET5_4 - public void Warning(FormattableString message, Exception exception = null) - => _manager.Warning(Name, message, exception); -#endif public void Warning(Exception exception) => _manager.Warning(Name, exception); - public void Info(string message, Exception exception = null) => _manager.Info(Name, message, exception); -#if DOTNET5_4 - public void Info(FormattableString message, Exception exception = null) - => _manager.Info(Name, message, exception); -#endif public void Info(Exception exception) => _manager.Info(Name, exception); - public void Verbose(string message, Exception exception = null) => _manager.Verbose(Name, message, exception); -#if DOTNET5_4 - public void Verbose(FormattableString message, Exception exception = null) - => _manager.Verbose(Name, message, exception); -#endif public void Verbose(Exception exception) => _manager.Verbose(Name, exception); - public void Debug(string message, Exception exception = null) => _manager.Debug(Name, message, exception); + public void Debug(Exception exception) + => _manager.Debug(Name, exception); + #if DOTNET5_4 + public void Log(LogSeverity severity, FormattableString message, Exception exception = null) + => _manager.Log(severity, Name, message, exception); + public void Error(FormattableString message, Exception exception = null) + => _manager.Error(Name, message, exception); + public void Warning(FormattableString message, Exception exception = null) + => _manager.Warning(Name, message, exception); + public void Info(FormattableString message, Exception exception = null) + => _manager.Info(Name, message, exception); + public void Verbose(FormattableString message, Exception exception = null) + => _manager.Verbose(Name, message, exception); public void Debug(FormattableString message, Exception exception = null) => _manager.Debug(Name, message, exception); #endif - public void Debug(Exception exception) - => _manager.Debug(Name, exception); } } diff --git a/src/Discord.Net/MessageQueue.cs b/src/Discord.Net/MessageQueue.cs index a595bb763..d4052ae41 100644 --- a/src/Discord.Net/MessageQueue.cs +++ b/src/Discord.Net/MessageQueue.cs @@ -33,11 +33,16 @@ namespace Discord.Net private readonly ConcurrentQueue _pendingEdits; private readonly ConcurrentQueue _pendingDeletes; private readonly ConcurrentDictionary _pendingSendsByNonce; - private int _nextWarning; - private int _count; + private int _count, _nextWarning; /// Gets the current number of queued actions. public int Count => _count; + /// Gets the current number of queued sends. + public int SendCount => _pendingSends.Count; + /// Gets the current number of queued edits. + public int EditCount => _pendingEdits.Count; + /// Gets the current number of queued deletes. + public int DeleteCount => _pendingDeletes.Count; internal MessageQueue(RestClient rest, Logger logger) { @@ -52,11 +57,12 @@ namespace Discord.Net _pendingSendsByNonce = new ConcurrentDictionary(); } - internal Message QueueSend(Channel channel, string text, bool isTTS) + internal Message QueueSend(ITextChannel channel, string text, bool isTTS) { - Message msg = new Message(0, channel, channel.IsPrivate ? channel.Client.PrivateUser : channel.Server.CurrentUser); + Message msg = new Message(0, channel, (channel as Channel).CurrentUser); + msg.IsTTS = isTTS; msg.RawText = text; - msg.Text = msg.Resolve(text); + msg.Text = Message.ResolveMentions(msg.Channel, msg.Text); msg.Nonce = GenerateNonce(); if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) { @@ -103,21 +109,24 @@ namespace Discord.Net } private Task RunSendQueue(CancellationToken cancelToken) { - return Task.Run((Func)(async () => + return Task.Run(async () => { - try + while (!cancelToken.IsCancellationRequested) { - while (!cancelToken.IsCancellationRequested) + Message msg; + while (_pendingSends.TryDequeue(out msg)) { - Message msg; - while (_pendingSends.TryDequeue(out msg)) + DecrementCount(); + string text; + if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail { - DecrementCount(); - string text; - if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail + try { - try + //msg.RawText = text; + //msg.Text = Message.ResolveMentions(msg.Channel, text); + var request = new SendMessageRequest(msg.Channel.Id) { +<<<<<<< HEAD msg.RawText = text; msg.Text = msg.Resolve(text); var request = new SendMessageRequest(msg.Channel.Id) @@ -136,80 +145,87 @@ namespace Discord.Net msg.State = MessageState.Failed; _logger.Error("Failed to send message to {msg.Server?.Id}/{msg.Channel?.Id}", ex); } +======= + Content = text, + Nonce = msg.Nonce.ToString(), + IsTTS = msg.IsTTS + }; + var response = await _rest.Send(request).ConfigureAwait(false); + msg.Id = response.Id; + msg.State = MessageState.Normal; + msg.Update(response); + } + catch (Exception ex) + { + msg.State = MessageState.Failed; + _logger.Error($"Failed to send message to {msg.Channel}", ex); +>>>>>>> 30ac95280e367253bcae6772cd04e706cfce6eff } } - await Task.Delay((int)Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } + await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } - catch (OperationCanceledException) { } - })); + }); } private Task RunEditQueue(CancellationToken cancelToken) { - return Task.Run((Func)(async () => + return Task.Run(async () => { - try + while (!cancelToken.IsCancellationRequested) { - while (!cancelToken.IsCancellationRequested) + MessageEdit edit; + while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued) { - MessageEdit edit; - while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued) + if (_pendingEdits.TryDequeue(out edit)) { - if (_pendingEdits.TryDequeue(out edit)) + DecrementCount(); + if (edit.Message.State == MessageState.Normal) { - DecrementCount(); - if (edit.Message.State == MessageState.Normal) + try { - try + var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id) { - var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id) - { - Content = edit.NewText - }; - await _rest.Send(request).ConfigureAwait(false); - } - catch (Exception ex) { _logger.Error("Failed to edit message", ex); } + Content = edit.NewText + }; + await _rest.Send(request).ConfigureAwait(false); } + catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message}", ex); } } } - await Task.Delay((int)Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } + await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } - catch (OperationCanceledException) { } - })); + }); } private Task RunDeleteQueue(CancellationToken cancelToken) { - return Task.Run((Func)(async () => + return Task.Run(async () => { - try + while (!cancelToken.IsCancellationRequested) { - while (!cancelToken.IsCancellationRequested) + Message msg; + while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued) { - Message msg; - while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued) + if (_pendingDeletes.TryDequeue(out msg)) { - if (_pendingDeletes.TryDequeue(out msg)) + DecrementCount(); + if (msg.State == MessageState.Normal) { - DecrementCount(); - if (msg.State == MessageState.Normal) + try { - try - { - var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); - await _rest.Send(request).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore - catch (Exception ex) { _logger.Error("Failed to delete message", ex); } + var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); + await _rest.Send(request).ConfigureAwait(false); + msg.State = MessageState.Deleted; } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore + catch (Exception ex) { _logger.Error($"Failed to delete message {msg}", ex); } } } - - await Task.Delay((int)Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } + + await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); } - catch (OperationCanceledException) { } - })); + }); } private void IncrementCount() @@ -217,8 +233,12 @@ namespace Discord.Net int count = Interlocked.Increment(ref _count); if (count >= _nextWarning) { - _nextWarning *= 2; - _logger.Warning($"Queue is backed up, currently at {count} actions."); + _nextWarning <<= 1; + int sendCount = _pendingSends.Count; + int editCount = _pendingEdits.Count; + int deleteCount = _pendingDeletes.Count; + count = sendCount + editCount + deleteCount; //May not add up due to async + _logger.Warning($"Queue is backed up, currently at {count} actions ({sendCount} sends, {editCount} edits, {deleteCount} deletes)."); } else if (count < WarningStart) //Reset once the problem is solved _nextWarning = WarningStart; @@ -226,7 +246,7 @@ namespace Discord.Net private void DecrementCount() { int count = Interlocked.Decrement(ref _count); - if (count < WarningStart) //Reset once the problem is solved + if (count < (WarningStart / 2)) //Reset once the problem is solved _nextWarning = WarningStart; } diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs deleted file mode 100644 index a6ff1162b..000000000 --- a/src/Discord.Net/Models/Channel.cs +++ /dev/null @@ -1,611 +0,0 @@ -using Discord.API.Client; -using Discord.API.Client.Rest; -using Discord.Net; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using APIChannel = Discord.API.Client.Channel; - -namespace Discord -{ - public class Channel : IMentionable - { - private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); - - private struct Member - { - public User User { get; } - public ChannelPermissions Permissions { get; } - - public Member(User user, ChannelPermissions permissions) - { - User = user; - Permissions = permissions; - } - } - - public class PermissionOverwrite - { - public PermissionTarget TargetType { get; } - public ulong TargetId { get; } - public ChannelPermissionOverrides Permissions { get; } - - internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny) - { - TargetType = targetType; - TargetId = targetId; - Permissions = new ChannelPermissionOverrides(allow, deny); - } - } - - private readonly ConcurrentDictionary _users; - private readonly ConcurrentDictionary _messages; - private Dictionary _permissionOverwrites; - - public DiscordClient Client { get; } - - /// Gets the unique identifier for this channel. - public ulong Id { get; } - /// Gets the server owning this channel, if this is a public chat. - public Server Server { get; } - /// Gets the target user, if this is a private chat. - public User Recipient { get; } - - /// Gets the name of this channel. - public string Name { get; private set; } - /// Gets the topic of this channel. - public string Topic { get; private set; } - /// Gets the position of this channel relative to other channels in this server. - public int Position { get; private set; } - /// Gets the type of this channel). - public ChannelType Type { get; private set; } - - /// Gets true if this is a private chat with another user. - public bool IsPrivate => Recipient != null; - /// Gets the string used to mention this channel. - public string Mention => $"<#{Id}>"; - /// Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. - public IEnumerable Messages => _messages?.Values ?? Enumerable.Empty(); - /// Gets a collection of all custom permissions used for this channel. - public IEnumerable PermissionOverwrites => _permissionOverwrites.Select(x => x.Value); - - /// Gets a collection of all users with read access to this channel. - public IEnumerable Users - { - get - { - if (IsPrivate) - return _users.Values.Select(x => x.User); - if (Client.Config.UsePermissionsCache) - { - if (Type == ChannelType.Text) - return _users.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User); - else if (Type == ChannelType.Voice) - return _users.Values.Select(x => x.User).Where(x => x.VoiceChannel == this); - } - else - { - if (Type == ChannelType.Text) - { - ChannelPermissions perms = new ChannelPermissions(); - return Server.Users.Where(x => - { - UpdatePermissions(x, ref perms); - return perms.ReadMessages == true; - }); - } - else if (Type == ChannelType.Voice) - return Server.Users.Where(x => x.VoiceChannel == this); - } - return Enumerable.Empty(); - } - } - - internal Channel(DiscordClient client, ulong id, Server server) - : this(client, id) - { - Server = server; - if (server != null) - { - foreach (var user in server.Users) - AddUser(user); - } - } - internal Channel(DiscordClient client, ulong id, User recipient) - : this(client, id) - { - Recipient = recipient; - AddUser(client.PrivateUser); - AddUser(recipient); - Type = ChannelType.Text; //Discord doesn't give us a type for private channels - } - private Channel(DiscordClient client, ulong id) - { - Client = client; - Id = id; - - _permissionOverwrites = new Dictionary(); - _users = new ConcurrentDictionary(); - if (client.Config.MessageCacheSize > 0) - _messages = new ConcurrentDictionary(); - } - - internal void Update(ChannelReference model) - { - if (!IsPrivate && model.Name != null) - Name = model.Name; - if (model.Type != null) - Type = model.Type; - } - internal void Update(APIChannel model) - { - Update(model as ChannelReference); - - if (model.Position != null) - Position = model.Position.Value; - if (model.Topic != null) - Topic = model.Topic; - if (model.Recipient != null) - { - Recipient.Update(model.Recipient); - Name = $"@{Recipient}"; - } - - if (model.PermissionOverwrites != null) - { - _permissionOverwrites = model.PermissionOverwrites - .Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny)) - .ToDictionary(x => x.TargetId); - UpdatePermissions(); - } - } - - /// Edits this channel, changing only non-null attributes. - public async Task Edit(string name = null, string topic = null, int? position = null) - { - if (name != null || topic != null) - { - var request = new UpdateChannelRequest(Id) - { - Name = name ?? Name, - Topic = topic ?? Topic, - Position = Position - }; - await Client.ClientAPI.Send(request).ConfigureAwait(false); - } - - if (position != null) - { - Channel[] channels = Server.AllChannels.Where(x => x.Type == Type).OrderBy(x => x.Position).ToArray(); - int oldPos = Array.IndexOf(channels, this); - var newPosChannel = channels.Where(x => x.Position > position).FirstOrDefault(); - int newPos = (newPosChannel != null ? Array.IndexOf(channels, newPosChannel) : channels.Length) - 1; - if (newPos < 0) - newPos = 0; - int minPos; - - if (oldPos < newPos) //Moving Down - { - minPos = oldPos; - for (int i = oldPos; i < newPos; i++) - channels[i] = channels[i + 1]; - channels[newPos] = this; - } - else //(oldPos > newPos) Moving Up - { - minPos = newPos; - for (int i = oldPos; i > newPos; i--) - channels[i] = channels[i - 1]; - channels[newPos] = this; - } - Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null; - await Server.ReorderChannels(channels.Skip(minPos), after).ConfigureAwait(false); - } - } - - public async Task Delete() - { - try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - #region Invites - /// Gets all active (non-expired) invites to this server. - public async Task> GetInvites() - => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); - - /// Creates a new invite to this channel. - /// Time (in seconds) until the invite expires. Set to null to never expire. - /// The max amount of times this invite may be used. Set to null to have unlimited uses. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to null. - public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) - { - if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); - if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - - var request = new CreateInviteRequest(Id) - { - MaxAge = maxAge ?? 0, - MaxUses = maxUses ?? 0, - IsTemporary = tempMembership, - WithXkcdPass = withXkcd - }; - - var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); - var invite = new Invite(Client, response.Code, response.XkcdPass); - return invite; - } - #endregion - - #region Messages - internal Message AddMessage(ulong id, User user, DateTime timestamp) - { - Message message = new Message(id, this, user); - message.State = MessageState.Normal; - var cacheLength = Client.Config.MessageCacheSize; - if (cacheLength > 0) - { - var oldestIds = _messages - .Where(x => x.Value.Timestamp < timestamp) - .Select(x => x.Key).OrderBy(x => x) - .Take(_messages.Count - cacheLength); - Message removed; - foreach (var removeId in oldestIds) - _messages.TryRemove(removeId, out removed); - return _messages.GetOrAdd(message.Id, message); - } - return message; - } - internal Message RemoveMessage(ulong id) - { - if (Client.Config.MessageCacheSize > 0) - { - Message msg; - if (_messages.TryRemove(id, out msg)) - return msg; - } - return new Message(id, this, null); - } - - public Message GetMessage(ulong id) - => GetMessage(id, null); - internal Message GetMessage(ulong id, ulong? userId) - { - if (Client.Config.MessageCacheSize > 0) - { - Message result; - if (_messages.TryGetValue(id, out result)) - return result; - } - return new Message(id, this, userId != null ? GetUser(userId.Value) : null); - } - - public async Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, - Relative relativeDir = Relative.Before, bool useCache = true) - { - if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); - if (limit == 0 || Type != ChannelType.Text) return new Message[0]; - - try - { - var request = new GetMessagesRequest(Id) - { - Limit = limit, - RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null, - RelativeId = relativeMessageId ?? 0 - }; - var msgs = await Client.ClientAPI.Send(request).ConfigureAwait(false); - return msgs.Select(x => - { - Message msg = null; - if (useCache) - { - msg = AddMessage(x.Id, GetUser(x.Author.Id), x.Timestamp.Value); - var user = msg.User; - if (user != null) - user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); - } - else - msg = new Message(x.Id, this, GetUser(x.Author.Id)); - msg.Update(x); - return msg; - }) - .ToArray(); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) - { - return new Message[0]; - } - } - - /// Returns all members of this channel with the specified name. - /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable FindUsers(string name, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch); - } - - public Task SendMessage(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); - return SendMessageInternal(text, false); - } - public Task SendTTSMessage(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); - return SendMessageInternal(text, true); - } - private Task SendMessageInternal(string text, bool isTTS) - { - if (text.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); - - return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS)); - } - - public async Task SendFile(string filePath) - { - using (var stream = File.OpenRead(filePath)) - return await SendFile(Path.GetFileName(filePath), stream).ConfigureAwait(false); - } - public async Task SendFile(string filename, Stream stream) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - if (stream == null) throw new ArgumentNullException(nameof(stream)); - - var request = new SendFileRequest(Id) - { - Filename = filename, - Stream = stream - }; - var model = await Client.ClientAPI.Send(request).ConfigureAwait(false); - - var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value); - msg.Update(model); - return msg; - } - - public Task SendIsTyping() - => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); - #endregion - - #region Permissions - internal void UpdatePermissions() - { - if (!Client.Config.UsePermissionsCache) - return; - - foreach (var pair in _users) - { - var member = pair.Value; - var perms = member.Permissions; - if (UpdatePermissions(member.User, ref perms)) - _users[pair.Key] = new Member(member.User, perms); - } - } - internal void UpdatePermissions(User user) - { - if (!Client.Config.UsePermissionsCache) - return; - - Member member; - if (_users.TryGetValue(user.Id, out member)) - { - var perms = member.Permissions; - if (UpdatePermissions(member.User, ref perms)) - _users[user.Id] = new Member(member.User, perms); - } - } - internal bool UpdatePermissions(User user, ref ChannelPermissions permissions) - { - uint newPermissions = 0; - var server = Server; - - //Load the mask of all permissions supported by this channel type - var mask = ChannelPermissions.All(this).RawValue; - - if (server != null) - { - //Start with this user's server permissions - newPermissions = server.GetPermissions(user).RawValue; - - if (IsPrivate || user == Server.Owner) - newPermissions = mask; //Owners always have all permissions - else - { - var channelOverwrites = PermissionOverwrites; - - var roles = user.Roles; - foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.DenyValue != 0 && roles.Any(y => y.Id == x.TargetId))) - newPermissions &= ~denyRole.Permissions.DenyValue; - foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.AllowValue != 0 && roles.Any(y => y.Id == x.TargetId))) - newPermissions |= allowRole.Permissions.AllowValue; - foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.DenyValue != 0)) - newPermissions &= ~denyUser.Permissions.DenyValue; - foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.AllowValue != 0)) - newPermissions |= allowUser.Permissions.AllowValue; - - if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions)) - newPermissions = mask; //ManageRolesOrPermissions gives all permisions - else if (Type == ChannelType.Text && !newPermissions.HasBit((byte)PermissionBits.ReadMessages)) - newPermissions = 0; //No read permission on a text channel removes all other permissions - else if (Type == ChannelType.Voice && !newPermissions.HasBit((byte)PermissionBits.Connect)) - newPermissions = 0; //No connect permissions on a voice channel removes all other permissions - else - newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example) - } - } - else - newPermissions = mask; //Private messages always have all permissions - - if (newPermissions != permissions.RawValue) - { - permissions = new ChannelPermissions(newPermissions); - return true; - } - return false; - } - internal ChannelPermissions GetPermissions(User user) - { - if (Client.Config.UsePermissionsCache) - { - Member member; - if (_users.TryGetValue(user.Id, out member)) - return member.Permissions; - else - return ChannelPermissions.None; - } - else - { - ChannelPermissions perms = new ChannelPermissions(); - UpdatePermissions(user, ref perms); - return perms; - } - } - - public ChannelPermissionOverrides GetPermissionsRule(User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - return PermissionOverwrites - .Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id) - .Select(x => x.Permissions) - .FirstOrDefault(); - } - public ChannelPermissionOverrides GetPermissionsRule(Role role) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - - return PermissionOverwrites - .Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id) - .Select(x => x.Permissions) - .FirstOrDefault(); - } - - public Task AddPermissionsRule(User user, ChannelPermissions allow, ChannelPermissions deny) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - return AddPermissionsRule(user.Id, PermissionTarget.User, allow.RawValue, deny.RawValue); - } - public Task AddPermissionsRule(User user, ChannelPermissionOverrides permissions) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - return AddPermissionsRule(user.Id, PermissionTarget.User, permissions.AllowValue, permissions.DenyValue); - } - public Task AddPermissionsRule(Role role, ChannelPermissions allow, ChannelPermissions deny) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - - return AddPermissionsRule(role.Id, PermissionTarget.Role, allow.RawValue, deny.RawValue); - } - public Task AddPermissionsRule(Role role, ChannelPermissionOverrides permissions) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - - return AddPermissionsRule(role.Id, PermissionTarget.Role, permissions.AllowValue, permissions.DenyValue); - } - private Task AddPermissionsRule(ulong targetId, PermissionTarget targetType, uint allow, uint deny) - { - var request = new AddChannelPermissionsRequest(Id) - { - TargetId = targetId, - TargetType = targetType.Value, - Allow = allow, - Deny = deny - }; - return Client.ClientAPI.Send(request); - } - - public Task RemovePermissionsRule(User user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return RemovePermissionsRule(user.Id, PermissionTarget.User); - } - public Task RemovePermissionsRule(Role role) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - return RemovePermissionsRule(role.Id, PermissionTarget.Role); - } - private async Task RemovePermissionsRule(ulong userOrRoleId, PermissionTarget targetType) - { - try - { - var perms = PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault(); - await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, userOrRoleId)).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - #endregion - - #region Users - internal void AddUser(User user) - { - if (!Client.Config.UsePermissionsCache) - return; - - var perms = new ChannelPermissions(); - UpdatePermissions(user, ref perms); - var member = new Member(user, ChannelPermissions.None); - _users[user.Id] = new Member(user, ChannelPermissions.None); - } - internal void RemoveUser(ulong id) - { - if (!Client.Config.UsePermissionsCache) - return; - - Member ignored; - _users.TryRemove(id, out ignored); - } - public User GetUser(ulong id) - { - if (!Client.Config.UsePermissionsCache) - { - if (Server != null) - { - var user = Server.GetUser(id); - if (user != null) - { - ChannelPermissions perms = new ChannelPermissions(); - UpdatePermissions(user, ref perms); - if (perms.ReadMessages) - return user; - } - } - else - { - if (id == Recipient.Id) - return Recipient; - else if (id == Client.PrivateUser.Id) - return Client.PrivateUser; - } - return null; - } - - Member result; - _users.TryGetValue(id, out result); - return result.User; - } - #endregion - - internal Channel Clone() - { - var result = new Channel(); - _cloner(this, result); - return result; - } - private Channel() { } - - public override string ToString() => Name ?? Id.ToIdString(); - } -} diff --git a/src/Discord.Net/Models/Color.cs b/src/Discord.Net/Models/Color.cs deleted file mode 100644 index 3555c5d8d..000000000 --- a/src/Discord.Net/Models/Color.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Discord -{ - public class Color - { - public static readonly Color Default = new Color(0); - - public static readonly Color Teal = new Color(0x1ABC9C); - public static readonly Color DarkTeal = new Color(0x11806A); - public static readonly Color Green = new Color(0x2ECC71); - public static readonly Color DarkGreen = new Color(0x1F8B4C); - public static readonly Color Blue = new Color(0x3498DB); - public static readonly Color DarkBlue = new Color(0x206694); - public static readonly Color Purple = new Color(0x9B59B6); - public static readonly Color DarkPurple = new Color(0x71368A); - public static readonly Color Magenta = new Color(0xE91E63); - public static readonly Color DarkMagenta = new Color(0xAD1457); - public static readonly Color Gold = new Color(0xF1C40F); - public static readonly Color DarkGold = new Color(0xC27C0E); - public static readonly Color Orange = new Color(0xE67E22); - public static readonly Color DarkOrange = new Color(0xA84300); - public static readonly Color Red = new Color(0xE74C3C); - public static readonly Color DarkRed = new Color(0x992D22); - - public static readonly Color LighterGrey = new Color(0x95A5A6); - public static readonly Color DarkGrey = new Color(0x607D8B); - public static readonly Color LightGrey = new Color(0x979C9F); - public static readonly Color DarkerGrey = new Color(0x546E7A); - - public uint RawValue { get; } - - public Color(uint rawValue) { RawValue = rawValue; } - public Color(byte r, byte g, byte b) : this(((uint)r << 16) | ((uint)g << 8) | b) { } - public Color(float r, float g, float b) : this((byte)(r * 255.0f), (byte)(g * 255.0f), (byte)(b * 255.0f)) { } - - /// Gets or sets the red component for this color. - public byte R => (byte)(RawValue >> 16); - /// Gets or sets the green component for this color. - public byte G => (byte)(RawValue >> 8); - /// Gets or sets the blue component for this color. - public byte B => (byte)(RawValue); - - private byte GetByte(int pos) => (byte)(RawValue >> (8 * (pos - 1))); - - public override string ToString() => '#' + RawValue.ToString("X"); - } -} diff --git a/src/Discord.Net/Net/Rest/RestClient.cs b/src/Discord.Net/Net/Rest/RestClient.cs index cb404a2f0..47853f8a4 100644 --- a/src/Discord.Net/Net/Rest/RestClient.cs +++ b/src/Discord.Net/Net/Rest/RestClient.cs @@ -63,26 +63,8 @@ namespace Discord.Net.Rest _engine = new BuiltInEngine(config, baseUrl, logger); #endif - if (_logger != null && _logger.Level >= LogSeverity.Verbose) - { - this.SentRequest += (s, e) => - { - string log = $"{e.Request.Method} {e.Request.Endpoint}: {e.Milliseconds} ms"; - if (_config.LogLevel >= LogSeverity.Debug) - { - if (e.Request is IRestFileRequest) - log += $" [{(e.Request as IRestFileRequest).Filename}]"; - else if (e.Response != null) - { - if (e.Request.IsPrivate) - log += $" [Hidden]"; - else - log += $" {e.ResponseJson}"; - } - } - _logger.Verbose(log); - }; - } + if (logger != null && logger.Level >= LogSeverity.Verbose) + SentRequest += (s, e) => _logger.Verbose($"{e.Request.Method} {e.Request.Endpoint}: {e.Milliseconds} ms"); } public async Task Send(IRestRequest request) diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index f0052f29a..aa2c9a98b 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -138,15 +138,11 @@ namespace Discord.Net.WebSockets protected virtual Task ProcessMessage(string json) { - if (Logger.Level >= LogSeverity.Debug) - Logger.Debug( $"In: {json}"); return TaskHelper.CompletedTask; } protected void QueueMessage(IWebSocketMessage message) { string json = JsonConvert.SerializeObject(new WebSocketMessage(message)); - if (Logger.Level >= LogSeverity.Debug) - Logger.Debug( $"Out: {json}"); _engine.QueueMessage(json); } diff --git a/src/Discord.Net/ServiceManager.cs b/src/Discord.Net/ServiceCollection.cs similarity index 91% rename from src/Discord.Net/ServiceManager.cs rename to src/Discord.Net/ServiceCollection.cs index 8bb7a678a..104f91dd4 100644 --- a/src/Discord.Net/ServiceManager.cs +++ b/src/Discord.Net/ServiceCollection.cs @@ -4,13 +4,13 @@ using System.Collections.Generic; namespace Discord { - public class ServiceManager : IEnumerable + internal class ServiceCollection : IEnumerable { private readonly Dictionary _services; internal DiscordClient Client { get; } - internal ServiceManager(DiscordClient client) + internal ServiceCollection(DiscordClient client) { Client = client; _services = new Dictionary(); diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 565bc2e86..80a6fb51d 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,9 +1,7 @@ { - "version": "0.9.0-rc3-3", + "version": "1.0.0-alpha1", "description": "An unofficial .Net API wrapper for the Discord client.", - "authors": [ - "RogueException" - ], + "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" diff --git a/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/test/Discord.Net.Tests/Discord.Net.Tests.csproj index 97d328c68..0c13b7075 100644 --- a/test/Discord.Net.Tests/Discord.Net.Tests.csproj +++ b/test/Discord.Net.Tests/Discord.Net.Tests.csproj @@ -37,7 +37,7 @@ - ..\..\..\DiscordBot\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll + ..\..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll True diff --git a/test/Discord.Net.Tests/Tests.cs b/test/Discord.Net.Tests/Tests.cs index e3d3977ad..51c045d69 100644 --- a/test/Discord.Net.Tests/Tests.cs +++ b/test/Discord.Net.Tests/Tests.cs @@ -42,7 +42,7 @@ namespace Discord.Tests //Create new server and invite the other bots to it _testServer = _hostClient.CreateServer("Discord.Net Testing", _hostClient.Regions.First()).Result; _testServerChannel = _testServer.DefaultChannel; - Invite invite = _testServer.CreateInvite(60, 1, false, false).Result; + var invite = _testServer.CreateInvite(60, 2, false, false).Result; WaitAll( _targetBot.GetInvite(invite.Code).Result.Accept(), _observerBot.GetInvite(invite.Code).Result.Accept()); @@ -122,6 +122,8 @@ namespace Discord.Tests _observerBot.Disconnect()); } + // Unit Test Helpers + private static void AssertEvent(string msg, Func action, Action> addEvent, Action> removeEvent, Func test = null) { AssertEvent(msg, action, addEvent, removeEvent, test, true); diff --git a/test/Discord.Net.Tests/packages.config b/test/Discord.Net.Tests/packages.config index 0eaf37a38..2abc396bb 100644 --- a/test/Discord.Net.Tests/packages.config +++ b/test/Discord.Net.Tests/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file