diff --git a/Discord.Net.sln b/Discord.Net.sln index bac38366c..1c32308ff 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -26,6 +26,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "src\Discord. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Modules", "src\Discord.Net.Modules\Discord.Net.Modules.xproj", "{01584E8A-78DA-486F-9EF9-A894E435841B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +67,18 @@ Global {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.Build.0 = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,5 +91,7 @@ Global {855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} {8D71A857-879A-4A10-859E-5FF824ED6688} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} {1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + {01584E8A-78DA-486F-9EF9-A894E435841B} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {3091164F-66AE-4543-A63D-167C1116241D} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} EndGlobalSection EndGlobal diff --git a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs index e242053d8..c9652a642 100644 --- a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs @@ -13,6 +13,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.8.0.0")] -[assembly: AssemblyFileVersion("0.8.0.0")] +[assembly: AssemblyVersion("0.8.1.0")] +[assembly: AssemblyFileVersion("0.8.1.0")] diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index aac273d4e..259f9606a 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.8.0-beta1", + "version": "0.8.1-beta1", "description": "A Discord.Net extension adding basic command support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -13,7 +13,7 @@ "warningsAsErrors": true }, "dependencies": { - "Discord.Net": "0.8.0-beta1" + "Discord.Net": "0.8.1-beta1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj b/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj new file mode 100644 index 000000000..4bc5bf376 --- /dev/null +++ b/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {3091164F-66AE-4543-A63D-167C1116241D} + Library + Properties + Discord + Discord.Net.Commands + 512 + v4.5 + False + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 4 + 6 + + + pdbonly + true + bin\Release\ + TRACE;NET45 + prompt + 4 + true + 6 + + + + + + + + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + + + + \ 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 new file mode 100644 index 000000000..f61c1c991 --- /dev/null +++ b/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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.8.1.0")] +[assembly: AssemblyFileVersion("0.8.1.0")] + diff --git a/src/Discord.Net.Modules/Discord.Net.Modules.xproj b/src/Discord.Net.Modules/Discord.Net.Modules.xproj new file mode 100644 index 000000000..f6db54ed1 --- /dev/null +++ b/src/Discord.Net.Modules/Discord.Net.Modules.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 01584e8a-78da-486f-9ef9-a894e435841b + Discord + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/Discord.Net.Modules/project.json b/src/Discord.Net.Modules/project.json new file mode 100644 index 000000000..9f71ee00c --- /dev/null +++ b/src/Discord.Net.Modules/project.json @@ -0,0 +1,22 @@ +{ + "version": "0.8.1-beta1", + "description": "A Discord.Net extension adding basic plugin support.", + "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" + }, + "compilationOptions": { + "warningsAsErrors": true + }, + "dependencies": { + "Discord.Net": "0.8.1-beta1" + }, + "frameworks": { + "net45": { }, + "dnx451": { } + } +} diff --git a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs index 857ea2201..2825ff2a9 100644 --- a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.8.0")] -[assembly: AssemblyFileVersion("0.8.0")] +[assembly: AssemblyVersion("0.8.1.0")] +[assembly: AssemblyFileVersion("0.8.1.0")] diff --git a/src/Discord.Net/API/Endpoints.cs b/src/Discord.Net/API/Endpoints.cs index 67c256a88..2ff2711bc 100644 --- a/src/Discord.Net/API/Endpoints.cs +++ b/src/Discord.Net/API/Endpoints.cs @@ -13,19 +13,22 @@ public const string Channels = "channels"; public static string Channel(string channelId) => $"channels/{channelId}"; - public static string ChannelTyping(string channelId) => $"channels/{channelId}/typing"; + public static string ChannelInvites(string channelId) => $"channels/{channelId}/invites"; public static string ChannelMessages(string channelId) => $"channels/{channelId}/messages"; public static string ChannelMessages(string channelId, int limit) => $"channels/{channelId}/messages?limit={limit}"; + public static string ChannelMessages(string channelId, int limit, string beforeId) => $"channels/{channelId}/messages?limit={limit}&before={beforeId}"; public static string ChannelMessage(string channelId, string msgId) => $"channels/{channelId}/messages/{msgId}"; public static string ChannelMessageAck(string channelId, string msgId) => $"channels/{channelId}/messages/{msgId}/ack"; - public static string ChannelInvites(string channelId) => $"channels/{channelId}/invites"; public static string ChannelPermission(string channelId, string userOrRoleId) => $"channels/{channelId}/permissions/{userOrRoleId}"; + public static string ChannelTyping(string channelId) => $"channels/{channelId}/typing"; public const string Servers = "guilds"; public static string Server(string serverId) => $"guilds/{serverId}"; + public static string ServerBan(string serverId, string userId) => $"guilds/{serverId}/bans/{userId}"; public static string ServerChannels(string serverId) => $"guilds/{serverId}/channels"; + public static string ServerInvites(string serverId) => $"guilds/{serverId}/invites"; public static string ServerMember(string serverId, string userId) => $"guilds/{serverId}/members/{userId}"; - public static string ServerBan(string serverId, string userId) => $"guilds/{serverId}/bans/{userId}"; + public static string ServerPrune(string serverId, int days) => $"guilds/{serverId}/prune?days={days}"; public static string ServerRoles(string serverId) => $"guilds/{serverId}/roles"; public static string ServerRole(string serverId, string roleId) => $"guilds/{serverId}/roles/{roleId}"; @@ -34,10 +37,10 @@ public static string InviteUrl(string inviteId) => $"https://discord.gg/{inviteId}"; public const string Users = "users"; - public static string UserMe => $"users/@me"; - public static string UserChannels(string userId) => $"users/{userId}/channels"; public static string UserAvatar(string userId, string avatarId) => $"users/{userId}/avatars/{avatarId}.jpg"; - + public static string UserChannels(string userId) => $"users/{userId}/channels"; + public static string UserMe => $"users/@me"; + public const string Voice = "voice"; public const string VoiceRegions = "voice/regions"; //public const string VoiceIce = "voice/ice"; diff --git a/src/Discord.Net/API/Invites.cs b/src/Discord.Net/API/Invites.cs index 62d2049b3..d2344dae2 100644 --- a/src/Discord.Net/API/Invites.cs +++ b/src/Discord.Net/API/Invites.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using System; +using System.Collections.Generic; namespace Discord.API { @@ -53,6 +54,7 @@ namespace Discord.API //Get public class GetInviteResponse : InviteReference { } + public class GetInvitesResponse : List { } //Accept public class AcceptInviteResponse : InviteReference { } diff --git a/src/Discord.Net/API/Members.cs b/src/Discord.Net/API/Members.cs index 2a48b653d..1f702719e 100644 --- a/src/Discord.Net/API/Members.cs +++ b/src/Discord.Net/API/Members.cs @@ -82,9 +82,20 @@ namespace Discord.API public IEnumerable Roles; } + public class PruneUsersResponse + { + [JsonProperty("pruned")] + public int? Pruned; + } + //Events internal sealed class MemberAddEvent : MemberInfo { } internal sealed class MemberUpdateEvent : MemberInfo { } internal sealed class MemberRemoveEvent : MemberInfo { } - internal sealed class MemberVoiceStateUpdateEvent : VoiceMemberInfo { } + internal sealed class MemberVoiceStateUpdateEvent : VoiceMemberInfo { } + internal sealed class MembersChunkEvent + { + [JsonProperty("members")] + public MemberInfo[] Members; + } } diff --git a/src/Discord.Net/API/Messages.cs b/src/Discord.Net/API/Messages.cs index 8fc031dd1..a5edb27ec 100644 --- a/src/Discord.Net/API/Messages.cs +++ b/src/Discord.Net/API/Messages.cs @@ -125,6 +125,21 @@ namespace Discord.API //Get public sealed class GetMessagesResponse : List { } + //Commands + internal sealed class GetUsersCommand : WebSocketMessage + { + public GetUsersCommand() : base(8) { } + public class Data + { + [JsonProperty("guild_id")] + public string ServerId; + [JsonProperty("query")] + public string Query; + [JsonProperty("limit")] + public int Limit; + } + } + //Events internal sealed class MessageCreateEvent : MessageInfo { } internal sealed class MessageUpdateEvent : MessageInfo { } diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index 8b3590a27..f547d81f7 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -92,11 +92,14 @@ namespace Discord var request = new ReorderChannelsRequest(channels); return _rest.Patch(Endpoints.ServerChannels(serverId), request); } - public Task GetMessages(string channelId, int count) + public Task GetMessages(string channelId, int count, string beforeMessageId = null) { if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - return _rest.Get(Endpoints.ChannelMessages(channelId, count)); + if (beforeMessageId != null) + return _rest.Get(Endpoints.ChannelMessages(channelId, count, beforeMessageId)); + else + return _rest.Get(Endpoints.ChannelMessages(channelId, count)); } //Incidents @@ -123,6 +126,12 @@ namespace Discord return _rest.Get(Endpoints.Invite(inviteIdOrXkcd)); } + public Task GetInvites(string serverId) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + return _rest.Get(Endpoints.ServerInvites(serverId)); + } public Task AcceptInvite(string inviteId) { if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); @@ -136,7 +145,7 @@ namespace Discord return _rest.Delete(Endpoints.Invite(inviteId)); } - //Members + //Users public Task EditUser(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); @@ -145,27 +154,37 @@ namespace Discord var request = new EditMemberRequest { Mute = mute, Deaf = deaf, Roles = roles }; return _rest.Patch(Endpoints.ServerMember(serverId, userId), request); } - public Task Kick(string serverId, string userId) + public Task KickUser(string serverId, string userId) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (userId == null) throw new ArgumentNullException(nameof(userId)); return _rest.Delete(Endpoints.ServerMember(serverId, userId)); } - public Task Ban(string serverId, string userId) + public Task BanUser(string serverId, string userId) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (userId == null) throw new ArgumentNullException(nameof(userId)); return _rest.Put(Endpoints.ServerBan(serverId, userId)); } - public Task Unban(string serverId, string userId) + public Task UnbanUser(string serverId, string userId) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (userId == null) throw new ArgumentNullException(nameof(userId)); return _rest.Delete(Endpoints.ServerBan(serverId, userId)); } + public Task PruneUsers(string serverId, int days, bool simulate) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days)); + + if (simulate) + return _rest.Get(Endpoints.ServerPrune(serverId, days)); + else + return _rest.Post(Endpoints.ServerPrune(serverId, days)); + } //Messages public Task SendMessage(string channelId, string message, IEnumerable mentionedUserIds = null, string nonce = null, bool isTTS = false) diff --git a/src/Discord.Net/DiscordClient.Bans.cs b/src/Discord.Net/DiscordClient.Bans.cs index e910d4bf6..6272f9d0b 100644 --- a/src/Discord.Net/DiscordClient.Bans.cs +++ b/src/Discord.Net/DiscordClient.Bans.cs @@ -38,7 +38,7 @@ namespace Discord if (user.Server == null) throw new ArgumentException("Unable to ban a user in a private chat."); CheckReady(); - return _api.Ban(user.Server.Id, user.Id); + return _api.BanUser(user.Server.Id, user.Id); } /// Unbans a user from the provided server. @@ -48,7 +48,7 @@ namespace Discord if (userId == null) throw new ArgumentNullException(nameof(userId)); CheckReady(); - try { await _api.Unban(server.Id, userId).ConfigureAwait(false); } + try { await _api.UnbanUser(server.Id, userId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } } diff --git a/src/Discord.Net/DiscordClient.Invites.cs b/src/Discord.Net/DiscordClient.Invites.cs index 10d1ee016..0d3b8804e 100644 --- a/src/Discord.Net/DiscordClient.Invites.cs +++ b/src/Discord.Net/DiscordClient.Invites.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net; using System.Threading.Tasks; @@ -10,8 +11,6 @@ namespace Discord /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode public async Task GetInvite(string inviteIdOrXkcd) { - //This doesn't work well if it's an invite to a different server! - if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); CheckReady(); @@ -26,7 +25,24 @@ namespace Discord var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); invite.Cache(); //Builds references - return invite; + invite.Update(response); + return invite; + } + + /// Gets all active (non-expired) invites to a provided server. + public async Task GetInvites(Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + CheckReady(); + + var response = await _api.GetInvites(server.Id).ConfigureAwait(false); + return response.Select(x => + { + var invite = new Invite(this, x.Code, x.XkcdPass, x.Guild.Id, x.Inviter?.Id, x.Channel?.Id); + invite.Cache(); //Builds references + invite.Update(x); + return invite; + }).ToArray(); } /// Creates a new invite to the default channel of the provided server. diff --git a/src/Discord.Net/DiscordClient.Messages.cs b/src/Discord.Net/DiscordClient.Messages.cs index 670164f4f..28b3c068c 100644 --- a/src/Discord.Net/DiscordClient.Messages.cs +++ b/src/Discord.Net/DiscordClient.Messages.cs @@ -46,7 +46,7 @@ namespace Discord public const int MaxMessageSize = 2000; public event EventHandler MessageReceived; - private void RaiseMessageCreated(Message msg) + private void RaiseMessageReceived(Message msg) { if (MessageReceived != null) RaiseEvent(nameof(MessageReceived), () => MessageReceived(this, new MessageEventArgs(msg))); @@ -93,6 +93,7 @@ namespace Discord { if (channel == null) throw new ArgumentNullException(nameof(channel)); if (text == null) throw new ArgumentNullException(nameof(text)); + if (text.Length > MaxMessageSize) throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less."); CheckReady(); return SendMessage(channel, text, false); @@ -102,6 +103,7 @@ namespace Discord { if (channel == null) throw new ArgumentNullException(nameof(channel)); if (text == null) throw new ArgumentNullException(nameof(text)); + if (text.Length > MaxMessageSize) throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less."); CheckReady(); return SendMessage(channel, text, false); @@ -111,7 +113,8 @@ namespace Discord { if (user == null) throw new ArgumentNullException(nameof(user)); if (text == null) throw new ArgumentNullException(nameof(text)); - CheckReady(); + if (text.Length > MaxMessageSize) throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less."); + CheckReady(); var channel = await CreatePMChannel(user).ConfigureAwait(false); return await SendMessage(channel, text).ConfigureAwait(false); @@ -164,6 +167,8 @@ namespace Discord public Task EditMessage(Message message, string text) { if (message == null) throw new ArgumentNullException(nameof(message)); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (text.Length > MaxMessageSize) throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less."); CheckReady(); if (text != null && text.Length > MaxMessageSize) @@ -210,7 +215,7 @@ namespace Discord { try { - var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); + var msgs = await _api.GetMessages(channel.Id, count, beforeMessageId).ConfigureAwait(false); return msgs.Select(x => { Message msg = null; diff --git a/src/Discord.Net/DiscordClient.Users.cs b/src/Discord.Net/DiscordClient.Users.cs index dba8f06a4..e63fd04c1 100644 --- a/src/Discord.Net/DiscordClient.Users.cs +++ b/src/Discord.Net/DiscordClient.Users.cs @@ -1,5 +1,4 @@ -using Discord.API; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -85,7 +84,7 @@ namespace Discord RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new UserEventArgs(user))); } public event EventHandler UserUpdated; - private void RaiseMemberUpdated(User user) + private void RaiseUserUpdated(User user) { if (UserUpdated != null) RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); @@ -170,10 +169,51 @@ namespace Discord if (user == null) throw new ArgumentNullException(nameof(user)); CheckReady(); - return _api.EditUser(user.Server?.Id, user.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); + var serverId = user.Server?.Id; + return _api.EditUser(serverId, user.Id, + mute: mute, deaf: deaf, + roles: roles.Select(x => x.Id).Where(x => x != serverId)); } - public Task EditProfile(string currentPassword = "", + public Task KickUser(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + return _api.KickUser(user.Server?.Id, user.Id); + } + public Task BanUser(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + return _api.BanUser(user.Server?.Id, user.Id); + } + public Task UnbanUser(Server server, string userId) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + return _api.UnbanUser(server.Id, userId); + } + + public async Task PruneUsers(string serverId, int days, bool simulate = false) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days)); + CheckReady(); + + var response = await _api.PruneUsers(serverId, days, simulate); + return response.Pruned ?? 0; + } + + /// When Config.UseLargeThreshold is enabled, running this command will request the Discord server to provide you with all offline users for a particular server. + public void RequestOfflineUsers(string serverId) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + _dataSocket.SendGetUsers(serverId); + } + + public Task EditProfile(string currentPassword = "", string username = null, string email = null, string password = null, ImageType avatarType = ImageType.Png, byte[] avatar = null) { diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index c162c237f..f14874dee 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -90,7 +90,7 @@ namespace Discord ChannelUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Channel Updated: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}"); MessageReceived += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Message Created: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); + $"Message Received: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); MessageDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Message Deleted: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); MessageUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, @@ -106,26 +106,26 @@ namespace Discord UserUnbanned += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Unbanned User: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"User Joined: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); + $"User Joined: {e.Server?.Name ?? "[Private]"}/{e.User.Name}"); UserRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"User Left: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); + $"User Left: {e.Server?.Name ?? "[Private]"}/{e.User.Name}"); UserUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"User Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); + $"User Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Name}"); UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"User Updated (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); + $"User Updated (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Name}"); ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, "Profile Updated"); } if (_config.LogLevel >= LogMessageSeverity.Verbose) { UserIsTypingUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, - $"Updated User (Is Typing): {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.User?.Name}"); + $"User Updated (Is Typing): {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.User?.Name}"); MessageReadRemotely += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Read Message (Remotely): {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); MessageSent += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Sent Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); UserPresenceUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, - $"Updated Member (Presence): {e.Server?.Name ?? "[Private]"}/{e.User?.Name}"); + $"User Updated (Presence): {e.Server?.Name ?? "[Private]"}/{e.User?.Name}"); _api.RestClient.OnRequest += (s, e) => { @@ -140,9 +140,9 @@ namespace Discord _channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Channel {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); _channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Channel {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); _channels.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Channels"); - _users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Member {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); - _users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Member {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); - _users.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Members"); + _users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created User {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); + _users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed User {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); + _users.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Users"); _messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}"); _messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}"); _messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Remapped Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/[{e.OldId} -> {e.NewId}]"); @@ -406,7 +406,7 @@ namespace Discord if (user != null) { user.Update(data); - RaiseMemberUpdated(user); + RaiseUserUpdated(user); } } break; @@ -418,6 +418,17 @@ namespace Discord RaiseUserRemoved(user); } break; + case "GUILD_MEMBERS_CHUNK": + { + var data = e.Payload.ToObject(_serializer); + foreach (var memberData in data.Members) + { + var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); + user.Update(memberData); + //RaiseUserAdded(user); + } + } + break; //Roles case "GUILD_ROLE_CREATE": @@ -499,7 +510,7 @@ namespace Discord } } - RaiseMessageCreated(msg); + RaiseMessageReceived(msg); if (Config.AckMessages && !isAuthor) await _api.AckMessage(data.Id, data.ChannelId).ConfigureAwait(false); diff --git a/src/Discord.Net/Helpers/Reference.cs b/src/Discord.Net/Helpers/Reference.cs index e02932e9b..4b2aecaa6 100644 --- a/src/Discord.Net/Helpers/Reference.cs +++ b/src/Discord.Net/Helpers/Reference.cs @@ -63,6 +63,6 @@ namespace Discord _getItem = getItem; _onCache = onCache; _onUncache = onUncache; - } + } } } diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index f08f1c6cc..97ad94a04 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -26,24 +26,54 @@ namespace Discord [JsonIgnore] public User Inviter => _inviter.Value; private readonly Reference _inviter; + private User _generatedInviter; /// Returns the server this invite is to. [JsonIgnore] public Server Server => _server.Value; private readonly Reference _server; + private Server _generatedServer; /// Returns the channel this invite is to. [JsonIgnore] public Channel Channel => _channel.Value; private readonly Reference _channel; + private Channel _generatedChannel; internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) : base(client, code) { XkcdCode = xkcdPass; - _server = new Reference(serverId, x => _client.Servers[x] ?? new Server(client, x)); - _inviter = new Reference(serverId, x => _client.Users[x, _server.Id] ?? new User(client, x, _server.Id)); - _channel = new Reference(serverId, x => _client.Channels[x] ?? new Channel(client, x, _server.Id, null)); + _server = new Reference(serverId, x => + { + var server = _client.Servers[x]; + if (server == null) + { + server = _generatedServer = new Server(client, x); + server.Cache(); + } + return server; + }); + _inviter = new Reference(serverId, x => + { + var inviter = _client.Users[x, _server.Id]; + if (inviter == null) + { + inviter = _generatedInviter = new User(client, x, _server.Id); + inviter.Cache(); + } + return inviter; + }); + _channel = new Reference(serverId, x => + { + var channel = _client.Channels[x]; + if (channel == null) + { + channel = _generatedChannel = new Channel(client, x, _server.Id, null); + channel.Cache(); + } + return channel; + }); } internal override void LoadReferences() { @@ -54,10 +84,21 @@ namespace Discord internal override void UnloadReferences() { } public override string ToString() => XkcdCode ?? Id; - - internal void Update(InviteInfo model) + + internal void Update(InviteReference model) + { + if (model.Guild != null && _generatedServer != null) + _generatedServer.Update(model.Guild); + if (model.Inviter != null && _generatedInviter != null) + _generatedInviter.Update(model.Inviter); + if (model.Channel != null && _generatedChannel != null) + _generatedChannel.Update(model.Channel); + } + internal void Update(InviteInfo model) { + Update(model as InviteReference); + if (model.IsRevoked != null) IsRevoked = model.IsRevoked.Value; if (model.IsTemporary != null) @@ -68,6 +109,6 @@ namespace Discord MaxUses = model.MaxUses.Value; if (model.Uses != null) Uses = model.Uses.Value; - } + } } } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index d6689d1a4..602f01db0 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -55,11 +55,6 @@ namespace Discord public IEnumerable VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); private ConcurrentDictionary _channels; - /// Returns a collection of all invites to this server. - [JsonIgnore] - public IEnumerable Invites => _invites.Values; - private ConcurrentDictionary _invites; - /// Returns a collection of all users within this server with their server-specific data. [JsonIgnore] public IEnumerable Members => _members.Select(x => x.Value); @@ -85,7 +80,6 @@ namespace Discord //Local Cache _bans = new ConcurrentDictionary(); - _invites = new ConcurrentDictionary(); } internal override void LoadReferences() { @@ -113,25 +107,26 @@ namespace Discord roles.Clear(); //Local Cache - var invites = _invites; - foreach (var invite in invites) - invite.Value.Uncache(); - invites.Clear(); - _bans.Clear(); _afkChannel.Unload(); } - internal void Update(GuildInfo model) + internal void Update(GuildReference model) + { + if (model.Name != null) + Name = model.Name; + } + + internal void Update(GuildInfo model) { + Update(model as GuildReference); + if (model.AFKTimeout != null) AFKTimeout = model.AFKTimeout.Value; if (model.AFKChannelId != null) if (model.JoinedAt != null) JoinedAt = model.JoinedAt.Value; - if (model.Name != null) - Name = model.Name; if (model.OwnerId != null && _ownerId != model.OwnerId) { _ownerId = model.OwnerId; @@ -212,9 +207,6 @@ namespace Discord _channels.TryRemove(channel.Id, out channel); } - internal void AddInvite(Invite invite) => _invites.TryAdd(invite.Id, invite); - internal void RemoveInvite(Invite invite) => _invites.TryRemove(invite.Id, out invite); - internal void AddMember(User user) { if (_members.TryAdd(user.Id, user)) diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 4fba8ae5b..1c28c5531 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -24,7 +24,7 @@ namespace Discord /// Returns the unique identifier for this user's current avatar. public string AvatarId { get; private set; } /// Returns the URL to this user's current avatar. - public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId); + public string AvatarUrl => AvatarId != null ? API.Endpoints.UserAvatar(Id, AvatarId) : null; /// Returns the datetime that this user joined this server. public DateTime JoinedAt { get; private set; } @@ -61,7 +61,8 @@ namespace Discord private readonly Reference _server; [JsonIgnore] - public Channel VoiceChannel { get; private set; } + public Channel VoiceChannel => _voiceChannel.Value; + private Reference _voiceChannel; [JsonIgnore] public IEnumerable Roles => _roles.Select(x => x.Value); @@ -130,6 +131,8 @@ namespace Discord if (Id == _client.CurrentUserId) x.CurrentUser = null; }); + _voiceChannel = new Reference(x => _client.Channels[x]); + Status = UserStatus.Offline; _channels = new ConcurrentDictionary(); if (serverId != null) @@ -210,16 +213,16 @@ namespace Discord SessionId = model.SessionId; if (model.Token != null) Token = model.Token; - - if (model.ChannelId != null) - VoiceChannel = _client.Channels[model.ChannelId]; + if (model.IsSelfDeafened != null) IsSelfDeafened = model.IsSelfDeafened.Value; if (model.IsSelfMuted != null) IsSelfMuted = model.IsSelfMuted.Value; if (model.IsServerSuppressed != null) IsServerSuppressed = model.IsServerSuppressed.Value; - } + + _voiceChannel.Id = model.ChannelId; //Can be null + } private void UpdateRoles(IEnumerable roles) { if (_server.Id != null) diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 49d368748..7e59565d7 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -27,7 +27,7 @@ namespace Discord.Net.WebSockets msg.Payload.Token = token; msg.Payload.Properties["$device"] = "Discord.Net"; if (_client.Config.UseLargeThreshold) - msg.Payload.LargeThreshold = 50; + msg.Payload.LargeThreshold = 100; msg.Payload.Compress = true; QueueMessage(msg); } @@ -141,5 +141,11 @@ namespace Discord.Net.WebSockets leaveVoice.Payload.ServerId = serverId; QueueMessage(leaveVoice); } + public void SendGetUsers(string serverId, string query = "", int limit = 0) + { + var getOfflineUsers = new GetUsersCommand(); + getOfflineUsers.Payload.ServerId = serverId; + QueueMessage(getOfflineUsers); + } } } diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 8a2b2e1b2..3d0b55689 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "0.8.0-beta1", + "version": "0.8.1-beta1", "description": "An unofficial .Net API wrapper for the Discord client.", "authors": [ "RogueException"