From ddf85f17ce37a8f92be1d715f1aee2f0c7a4382d Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 22 Nov 2017 01:28:30 +0100 Subject: [PATCH 1/2] Fixed usersync exception, Added Attachments --- DiscordSharp/DiscordClient.cs | 174 +++++++++++++----- DiscordSharp/DiscordSharp.csproj | 1 + .../Events/DiscordPrivateMessageEventArgs.cs | 1 + DiscordSharp/Objects/MessageAttachment.cs | 33 ++++ 4 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 DiscordSharp/Objects/MessageAttachment.cs diff --git a/DiscordSharp/DiscordClient.cs b/DiscordSharp/DiscordClient.cs index a9d8c14..9635e42 100644 --- a/DiscordSharp/DiscordClient.cs +++ b/DiscordSharp/DiscordClient.cs @@ -14,6 +14,7 @@ using ID = System.String; using DiscordSharp.Sockets; +using System.Linq; namespace DiscordSharp { @@ -42,12 +43,13 @@ public string Device { get; set; } = "DiscordSharp Bot"; /// - /// + /// /// [JsonProperty("$referrer")] public string referrer { get; set; } + /// - /// + /// /// [JsonProperty("$referring_domain")] public string referring_domain { get; set; } @@ -79,6 +81,7 @@ public enum DiscordMessageType /// Private/DM /// PRIVATE, + /// /// Public/in a channel. /// @@ -86,7 +89,7 @@ public enum DiscordMessageType } /// - /// Where the magic happens. + /// Where the magic happens. /// public class DiscordClient { @@ -94,6 +97,7 @@ public class DiscordClient /// The token associated with authenticating your bot and ensuring they can send messages. /// public static string token { get; internal set; } = null; + /// /// If this is true, the account this client is running on is a special bot account. /// @@ -162,12 +166,11 @@ public class DiscordClient /// public bool Autoconnect { get; set; } = true; - private IDiscordWebSocket ws; private List ServersList { get; set; } private string CurrentGameName = ""; private int? IdleSinceUnixTime = null; - static string UserAgentString = $" (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version.ToString()})"; + private static string UserAgentString = $" (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version.ToString()})"; private DiscordVoiceClient VoiceClient; private Logger DebugLogger = new Logger(); private CancellationTokenSource KeepAliveTaskTokenSource = new CancellationTokenSource(); @@ -198,74 +201,116 @@ public class DiscordClient /// The key is the id of the message, and the value is a DiscordMessage object. If you need raw json, this is contained inside of the DiscordMessage object now. /// private Dictionary MessageLog = new Dictionary(); + //private List> MessageLog = new List>(); private List PrivateChannels = new List(); #region Event declaration + public event EventHandler MessageReceived; + public event EventHandler Connected; + public event EventHandler SocketOpened; + public event EventHandler SocketClosed; + public event EventHandler ChannelCreated; + public event EventHandler PrivateChannelCreated; + public event EventHandler PrivateMessageReceived; + public event EventHandler KeepAliveSent; + public event EventHandler MentionReceived; + public event EventHandler UserTypingStart; + public event EventHandler MessageEdited; + public event EventHandler PresenceUpdated; + public event EventHandler URLMessageAutoUpdate; + public event EventHandler VoiceStateUpdate; + public event EventHandler UnknownMessageTypeReceived; + public event EventHandler MessageDeleted; + public event EventHandler UserUpdate; + public event EventHandler UserAddedToServer; + public event EventHandler UserRemovedFromServer; + public event EventHandler GuildCreated; + /// /// Occurs when a guild becomes available after being unavailable. /// public event EventHandler GuildAvailable; + public event EventHandler GuildDeleted; + public event EventHandler ChannelUpdated; + public event EventHandler TextClientDebugMessageReceived; + public event EventHandler VoiceClientDebugMessageReceived; + public event EventHandler ChannelDeleted; + public event EventHandler GuildUpdated; + public event EventHandler RoleDeleted; + public event EventHandler RoleUpdated; + public event EventHandler GuildMemberUpdated; + public event EventHandler GuildMemberBanned; + public event EventHandler PrivateChannelDeleted; + public event EventHandler BanRemoved; + public event EventHandler PrivateMessageDeleted; #region Voice + /// /// For use when connected to voice only. /// public event EventHandler AudioPacketReceived; + /// /// For use when connected to voice only. /// public event EventHandler UserSpeaking; + /// /// For use when connected to voice only. /// public event EventHandler UserLeftVoiceChannel; + /// /// Occurs when the voice client is fully connected to voice. /// public event EventHandler VoiceClientConnected; + /// /// Occurs when the voice queue is emptied. /// public event EventHandler VoiceQueueEmpty; - #endregion - #endregion + + #endregion Voice + + #endregion Event declaration /// - /// + /// /// /// If you have a token you wish to use, provide it here. Else, a login attempt will be made. /// Set this to true if your bot is going to be a bot account @@ -314,7 +359,7 @@ public DiscordClient(string tokenOverride = null, bool isBotAccount = false, boo public List GetPrivateChannels() => PrivateChannels; /// - /// + /// /// /// True if connected to voice. public bool ConnectedToVoice() => VoiceClient != null ? VoiceClient.Connected : false; @@ -417,11 +462,11 @@ private void GetChannelsList(JObject m) if (!presence["game"].IsNullOrEmpty()) { member.CurrentGame = presence["game"]["name"].ToString(); - if (presence["d"]["game"]["type"].ToObject() == 1) + if (presence["game"]["type"].ToObject() == 1) { member.Streaming = true; - if (presence["d"]["game"]["url"].ToString() != null) - member.StreamURL = presence["d"]["game"]["url"].ToString(); + if (presence["game"]["url"].ToString() != null) + member.StreamURL = presence["game"]["url"].ToString(); } } } @@ -464,7 +509,6 @@ private void GetChannelsList(JObject m) } PrivateChannels.Add(tempPrivate); } - } /// @@ -517,7 +561,6 @@ public void DeleteServer(string ServerID) } } - /// /// Sends a message to a channel, what else did you expect? /// @@ -652,7 +695,7 @@ public void ChangeGuildIcon(Bitmap image, DiscordServer guild) } /// - /// Returns a List of DiscordMessages. + /// Returns a List of DiscordMessages. /// /// The channel to return them from. /// How many to return @@ -1060,7 +1103,6 @@ private void PresenceUpdateEvents(JObject message) } } - /// /// Deletes a message with a specified ID. /// This method will only work if the message was sent since the bot has ran. @@ -1135,7 +1177,7 @@ public int DeleteMultipleMessagesInChannel(DiscordChannel channel, int count) } /// - /// + /// /// /// /// @@ -1159,7 +1201,7 @@ public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string use DebugLogger.Log("Error in GetMemberFromChannel: foundMember was null!", MessageLevel.Error); } } - else if(channel.GetType() == typeof(DiscordPrivateChannel)) + else if (channel.GetType() == typeof(DiscordPrivateChannel)) { return ((DiscordPrivateChannel)channel).Recipient; } @@ -1172,7 +1214,7 @@ public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string use } /// - /// + /// /// /// /// @@ -1183,7 +1225,6 @@ public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string id) { if (channel.GetType() == typeof(DiscordChannel)) //regular { - DiscordMember foundMember = ((DiscordChannel)channel).Parent.GetMemberByKey(id); if (foundMember != null) return foundMember; @@ -1192,7 +1233,7 @@ public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string id) DebugLogger.Log($"Error in GetMemberFromChannel: foundMember was null! ID: {id}", MessageLevel.Error); } } - else if(channel.GetType() == typeof(DiscordPrivateChannel)) + else if (channel.GetType() == typeof(DiscordPrivateChannel)) { return ((DiscordPrivateChannel)channel).Recipient; } @@ -1222,7 +1263,7 @@ public DiscordChannel GetChannelByName(string channelName) } /// - /// + /// /// /// /// @@ -1258,7 +1299,7 @@ public void AcceptInvite(string inviteID) } /// - /// + /// /// /// The last DiscordMessage sent public DiscordMessage GetLastMessageSent() @@ -1274,7 +1315,7 @@ public DiscordMessage GetLastMessageSent() } /// - /// + /// /// /// /// The last DiscordMessage sent in the given channel @@ -1397,7 +1438,6 @@ private void MessageUpdateEvents(JObject message) DiscordMessage newMessage = toRemove; newMessage.Content = jsonToEdit["d"]["content"].ToString(); MessageLog.Add(message["d"]["id"].ToString(), newMessage); - } else //I know they say assume makes an ass out of you and me...but we're assuming it's Discord's weird auto edit of a just URL message { @@ -1466,18 +1506,23 @@ private void MessageCreateEvents(JObject message) if (message["d"]["author"]["id"].ToString() != Me.ID) { var foundPM = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - DiscordPrivateMessageEventArgs dpmea = new DiscordPrivateMessageEventArgs(); - dpmea.Channel = foundPM; - dpmea.Message = message["d"]["content"].ToString(); - DiscordMember tempMember = new DiscordMember(this); - tempMember.Username = message["d"]["author"]["username"].ToString(); - tempMember.ID = message["d"]["author"]["id"].ToString(); + var dpmea = new DiscordPrivateMessageEventArgs + { + Channel = foundPM, + Message = message["d"]["content"].ToString(), + Attachments = message["d"]["attachments"].AsEnumerable().Select(m => m.ToObject()).ToArray() + }; + var tempMember = new DiscordMember(this) + { + Username = message["d"]["author"]["username"].ToString(), + ID = message["d"]["author"]["id"].ToString() + }; dpmea.Author = tempMember; tempMember.parentclient = this; + dpmea.RawJson = message; - if (PrivateMessageReceived != null) - PrivateMessageReceived(this, dpmea); + PrivateMessageReceived?.Invoke(this, dpmea); } else { @@ -1589,7 +1634,9 @@ private void ChannelCreateEvents(JObject message) PrivateChannelCreated(this, fak); } } - #endregion + + #endregion Message Received Crap.. + private string GetGatewayUrl() { if (token == null) @@ -1628,7 +1675,7 @@ private string GetGatewayUrl() } /// - /// + /// /// /// /// @@ -1857,7 +1904,6 @@ public void DeleteInvite(string id) /// A full invite URL. public string MakeInviteURLFromCode(string id) => "https://discord.gg/" + id; - /// /// Runs the websocket connection for the client hooking up the appropriate events. /// @@ -1891,7 +1937,7 @@ public void Connect(bool useDotNetWebsocket = false) { message = JObject.Parse(e.Message); } - catch(Exception ex) + catch (Exception ex) { DebugLogger.Log($"MessageReceived Error: {ex.Message}\n\n```{e.Message}\n```\n", MessageLevel.Error); } @@ -1907,7 +1953,6 @@ public void Connect(bool useDotNetWebsocket = false) if (!message["s"].IsNullOrEmpty()) Sequence = message["s"].ToObject(); - }; ws.SocketOpened += (sender, e) => { @@ -1998,45 +2043,57 @@ private void ClientPacketReceived(JObject message) ReadyComplete = true; - Task.Run(() => + Task.Run(async () => { - Task.Delay(3000); + await Task.Delay(3000); Connected?.Invoke(this, new DiscordConnectEventArgs { User = Me }); }); //fire and forget waiting of up to 3 seconds for guilds to become available. break; + case ("GUILD_MEMBERS_CHUNK"): GuildMemberChunkEvents(message); break; + case ("GUILD_MEMBER_REMOVE"): GuildMemberRemoveEvents(message); break; + case ("GUILD_MEMBER_ADD"): GuildMemberAddEvents(message); break; + case ("GUILD_DELETE"): GuildDeleteEvents(message); break; + case ("GUILD_CREATE"): GuildCreateEvents(message); break; + case ("GUILD_MEMBER_UPDATE"): GuildMemberUpdateEvents(message); break; + case ("GUILD_UPDATE"): GuildUpdateEvents(message); break; + case ("GUILD_ROLE_DELETE"): GuildRoleDeleteEvents(message); break; + case ("GUILD_ROLE_UPDATE"): GuildRoleUpdateEvents(message); break; + case ("PRESENCE_UPDATE"): PresenceUpdateEvents(message); break; + case ("MESSAGE_UPDATE"): MessageUpdateEvents(message); break; + case ("TYPING_START"): DiscordServer server = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); if (server != null) @@ -2047,38 +2104,50 @@ private void ClientPacketReceived(JObject message) UserTypingStart(this, new DiscordTypingStartEventArgs { user = uuser, Channel = channel, Timestamp = int.Parse(message["d"]["timestamp"].ToString()) }); } break; + case ("MESSAGE_CREATE"): MessageCreateEvents(message); break; + case ("CHANNEL_CREATE"): ChannelCreateEvents(message); break; + case ("VOICE_STATE_UPDATE"): VoiceStateUpdateEvents(message); break; + case ("VOICE_SERVER_UPDATE"): VoiceServerUpdateEvents(message); break; + case ("MESSAGE_DELETE"): MessageDeletedEvents(message); break; + case ("USER_UPDATE"): UserUpdateEvents(message); break; + case ("CHANNEL_UPDATE"): ChannelUpdateEvents(message); break; + case ("CHANNEL_DELETE"): ChannelDeleteEvents(message); break; + case ("GUILD_BAN_ADD"): GuildMemberBannedEvents(message); break; + case ("GUILD_BAN_REMOVE"): GuildMemberBanRemovedEvents(message); break; + case ("MESSAGE_ACK"): //ignore this message, it's irrelevant break; + default: if (UnknownMessageTypeReceived != null) UnknownMessageTypeReceived(this, new UnknownMessageEventArgs { RawJson = message }); @@ -2209,7 +2278,8 @@ private void GuildMemberBannedEvents(JObject message) private void VoiceServerUpdateEvents(JObject message) { // TODO VoiceClient is null when disconnecting from voice - if (VoiceClient == null) { + if (VoiceClient == null) + { return; } VoiceClient.VoiceEndpoint = message["d"]["endpoint"].ToString(); @@ -2234,12 +2304,14 @@ private void VoiceServerUpdateEvents(JObject message) } #if NETFX4_5 + private void ConnectToVoiceAsync() { VoiceClient.InitializeOpusEncoder(); VoiceThread = new Thread(() => VoiceClient.Initiate()); VoiceThread.Start(); } + #else private Task ConnectToVoiceAsync() { @@ -2249,7 +2321,7 @@ private Task ConnectToVoiceAsync() #endif /// - /// Kicks a specified DiscordMember from the guild that's assumed from their + /// Kicks a specified DiscordMember from the guild that's assumed from their /// parent property. /// /// @@ -2507,7 +2579,6 @@ public void DisconnectFromVoice() VoiceClient.Dispose(); VoiceClient = null; - ws.Send(disconnectMessage); } catch @@ -2522,7 +2593,7 @@ public void DisconnectFromVoice() } /// - /// + /// /// /// The current VoiceClient or null. public DiscordVoiceClient GetVoiceClient() @@ -2541,7 +2612,7 @@ private void GuildMemberUpdateEvents(JObject message) if (memberUpdated != null) { memberUpdated.Username = message["d"]["user"]["username"].ToString(); - if(message["d"]["nick"] != null) + if (message["d"]["nick"] != null) { if (message["d"]["nick"].ToString() == null) memberUpdated.Nickname = ""; //No nickname @@ -2553,7 +2624,7 @@ private void GuildMemberUpdateEvents(JObject message) memberUpdated.Avatar = message["d"]["user"]["avatar"].ToString(); memberUpdated.Discriminator = message["d"]["user"]["discriminator"].ToString(); memberUpdated.ID = message["d"]["user"]["id"].ToString(); - + foreach (var roles in message["d"]["roles"]) { memberUpdated.Roles.Add(server.Roles.Find(x => x.ID == roles.ToString())); @@ -2959,7 +3030,7 @@ private void GuildCreateEvents(JObject message) foreach (var mbr in message["d"]["members"]) { DiscordMember member = JsonConvert.DeserializeObject(mbr["user"].ToString()); - if(mbr["nick"] != null) + if (mbr["nick"] != null) member.Nickname = mbr["nick"].ToString(); member.parentclient = this; @@ -2973,7 +3044,8 @@ private void GuildCreateEvents(JObject message) member.Roles.Add(server.Roles.Find(x => x.Name == "@everyone")); server.AddMember(member); } - foreach (var voiceStateJSON in message["d"]["voice_states"]) { + foreach (var voiceStateJSON in message["d"]["voice_states"]) + { DiscordVoiceState voiceState = JsonConvert.DeserializeObject(voiceStateJSON.ToString()); DiscordMember member = server.GetMemberByKey(voiceState.UserID); @@ -3009,7 +3081,6 @@ private void GuildMemberAddEvents(JObject message) DiscordMember existingMember = e.Guild.GetMemberByKey(message["d"]["user"]["id"].ToString()); if (existingMember != null) { - DiscordMember newMember = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); newMember.parentclient = this; e.AddedMember = newMember; @@ -3026,6 +3097,7 @@ private void GuildMemberAddEvents(JObject message) DebugLogger.Log($"Skipping guild member add because user already exists in server.", MessageLevel.Debug); } } + private void GuildMemberRemoveEvents(JObject message) { DiscordGuildMemberRemovedEventArgs e = new DiscordGuildMemberRemovedEventArgs(); @@ -3194,6 +3266,7 @@ private void VoiceStateUpdateEvents(JObject message) VoiceStateUpdate(this, e); } } + private JObject ServerInfo(string channelOrServerId) { string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{channelOrServerId}"; @@ -3210,6 +3283,7 @@ private JObject ServerInfo(string channelOrServerId) private int HeartbeatInterval = 41250; private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private void KeepAlive() { //string keepAliveJson = "{\"op\":" + Opcodes.HEARTBEAT + ", \"d\":" + Sequence + "}"; @@ -3319,4 +3393,4 @@ public string SendLoginRequest() return token; } } -} +} \ No newline at end of file diff --git a/DiscordSharp/DiscordSharp.csproj b/DiscordSharp/DiscordSharp.csproj index 03d32f6..70d4ba4 100644 --- a/DiscordSharp/DiscordSharp.csproj +++ b/DiscordSharp/DiscordSharp.csproj @@ -108,6 +108,7 @@ + diff --git a/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs b/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs index f51698f..b950c69 100644 --- a/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs +++ b/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs @@ -9,6 +9,7 @@ public class DiscordPrivateMessageEventArgs : EventArgs public DiscordPrivateChannel Channel { get; internal set; } public DiscordMember Author { get; internal set; } public string Message { get; internal set; } + public MessageAttachment[] Attachments { get; internal set; } public JObject RawJson { get; internal set; } } } \ No newline at end of file diff --git a/DiscordSharp/Objects/MessageAttachment.cs b/DiscordSharp/Objects/MessageAttachment.cs new file mode 100644 index 0000000..9c8e734 --- /dev/null +++ b/DiscordSharp/Objects/MessageAttachment.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace DiscordSharp.Objects +{ + public class MessageAttachment + { + [JsonProperty("filename")] + public string Filename { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("proxy_url")] + public string ProxyUrl { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + } +} \ No newline at end of file From ed2388645c2666b8f227537b1166d63468269c41 Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 22 Nov 2017 19:31:30 +0100 Subject: [PATCH 2/2] Splitted DiscordClient into partial classes --- DiscordSharp.Commands/CommandsManager.cs | 40 +- DiscordSharp/Discord/DiscordClient.cs | 2099 ++++++++++ .../Discord/DiscordClientEventHandlers.cs | 118 + .../Discord/DiscordClientPacketHandlers.cs | 1208 ++++++ DiscordSharp/DiscordClient.cs | 3396 ----------------- DiscordSharp/DiscordSharp.csproj | 5 +- .../Events/DiscordPrivateMessageEventArgs.cs | 88 + DiscordSharp/Objects/DiscordChannel.cs | 24 +- DiscordSharp/Objects/DiscordMember.cs | 30 +- DiscordSharp/Objects/DiscordMessageType.cs | 24 + DiscordSharp/Objects/DiscordProperties.cs | 63 + DiscordSharp/Objects/DiscordServer.cs | 22 +- DiscordSharp/Voice/DiscordVoiceClient.cs | 186 +- DiscordSharp/WebWrapper.cs | 12 +- DiscordSharpTestApplication/LuigibotMain.cs | 122 +- .../Modules/EvalModules.cs | 4 +- .../Modules/TestServerLog.cs | 7 +- DiscordSharpTestApplication/Modules/Voice.cs | 7 +- DiscordSharpTestApplication/Program.cs | 9 +- 19 files changed, 3885 insertions(+), 3579 deletions(-) create mode 100644 DiscordSharp/Discord/DiscordClient.cs create mode 100644 DiscordSharp/Discord/DiscordClientEventHandlers.cs create mode 100644 DiscordSharp/Discord/DiscordClientPacketHandlers.cs delete mode 100644 DiscordSharp/DiscordClient.cs create mode 100644 DiscordSharp/Objects/DiscordMessageType.cs create mode 100644 DiscordSharp/Objects/DiscordProperties.cs diff --git a/DiscordSharp.Commands/CommandsManager.cs b/DiscordSharp.Commands/CommandsManager.cs index 9ad7eeb..15dd7bc 100644 --- a/DiscordSharp.Commands/CommandsManager.cs +++ b/DiscordSharp.Commands/CommandsManager.cs @@ -4,16 +4,19 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using DiscordSharp.Discord; namespace DiscordSharp.Commands { public class ModuleNotEnabledException : Exception { - IModule module; + private IModule module; + public IModule Module { get { return module; } } + public ModuleNotEnabledException(string message, IModule module) : base(message) { this.module = module; @@ -22,12 +25,15 @@ public ModuleNotEnabledException(string message, IModule module) : base(message) public class BaseModuleToggleException : Exception { - public BaseModuleToggleException(string message) : base(message) { } + public BaseModuleToggleException(string message) : base(message) + { + } } public class CommandsManager { private readonly DiscordClient __client; + public DiscordClient Client { get @@ -39,6 +45,7 @@ public DiscordClient Client public Random rng = new Random((int)DateTime.Now.Ticks); private List __commands; + public List Commands { get { return __commands; } @@ -50,13 +57,15 @@ public List Commands /// Value = Whether or not the module is enabled. /// private Dictionary __modules; + public Dictionary Modules { get { return __modules; } } - //id, permission + //id, permission private static Dictionary __internalUserRoles; + public static Dictionary UserRoles { get @@ -69,7 +78,7 @@ internal static PermissionType GetPermissionFromID(string id) { if (__internalUserRoles.Count > 0) { - foreach(var perm in __internalUserRoles) + foreach (var perm in __internalUserRoles) { if (perm.Key == id) return perm.Value; @@ -91,7 +100,7 @@ public CommandsManager(DiscordClient client) public bool HasPermission(DiscordMember member, PermissionType permission) { - if(__internalUserRoles.ContainsKey(member.ID)) + if (__internalUserRoles.ContainsKey(member.ID)) { foreach (var perm in __internalUserRoles) if (perm.Key == member.ID && (int)perm.Value >= (int)permission) @@ -106,6 +115,7 @@ public void AddPermission(DiscordMember member, PermissionType permission) __internalUserRoles.Remove(member.ID); __internalUserRoles.Add(member.ID, permission); } + public void AddPermission(string memberID, PermissionType permission) { if (__internalUserRoles.ContainsKey(memberID)) @@ -131,7 +141,7 @@ public Dictionary ModuleDictionaryForJson() { Dictionary dict = new Dictionary(); - lock(__modules) + lock (__modules) { foreach (var kvp in __modules) { @@ -152,7 +162,7 @@ public int ExecuteOnMessageCommand(string rawCommandText, DiscordChannel channel if (command != null && command.Parent != null) //if it's a generic command without a parent then don't bother doing this. { - lock(__modules) + lock (__modules) { if (__modules[command.Parent] == false) { @@ -161,7 +171,7 @@ public int ExecuteOnMessageCommand(string rawCommandText, DiscordChannel channel } } - if(command != null) + if (command != null) { command.Args.Clear(); if (command.ArgCount > 0) @@ -176,11 +186,11 @@ public int ExecuteOnMessageCommand(string rawCommandText, DiscordChannel channel return 0; } } - catch(UnauthorizedAccessException uaex) + catch (UnauthorizedAccessException uaex) { throw uaex; //no permission } - catch(Exception ex) + catch (Exception ex) { throw ex; } @@ -189,7 +199,7 @@ public int ExecuteOnMessageCommand(string rawCommandText, DiscordChannel channel public bool ModuleEnabled(string name) { - lock(__modules) + lock (__modules) { foreach (var kvp in __modules) { @@ -204,7 +214,7 @@ public bool ModuleEnabled(string name) public void EnableModule(string name) { - lock(__modules) + lock (__modules) { foreach (var kvp in __modules) { @@ -222,7 +232,7 @@ public void DisableModule(string name) if (name.ToLower().Trim() == "base") throw new BaseModuleToggleException("Can't disable base module!"); - lock(__modules) + lock (__modules) { foreach (var kvp in __modules) { @@ -250,7 +260,7 @@ public void AddCommand(ICommand command, IModule fromModule) { command.Parent = fromModule; command.Parent.Commands.Add(command); - lock(__modules) + lock (__modules) { if (!__modules.ContainsKey(fromModule)) __modules.Add(fromModule, true); @@ -262,4 +272,4 @@ public void AddCommand(ICommand command, IModule fromModule) __commands.Add(command); } } -} +} \ No newline at end of file diff --git a/DiscordSharp/Discord/DiscordClient.cs b/DiscordSharp/Discord/DiscordClient.cs new file mode 100644 index 0000000..a19027b --- /dev/null +++ b/DiscordSharp/Discord/DiscordClient.cs @@ -0,0 +1,2099 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using DiscordSharp.Events; +using DiscordSharp.Objects; +using DiscordSharp.Sockets; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ID = System.String; + +namespace DiscordSharp.Discord +{ + /// + /// Where the magic happens. + /// + public partial class DiscordClient + { + /// + /// The token associated with authenticating your bot and ensuring they can send messages. + /// + public static string token { get; internal set; } + + /// + /// If this is true, the account this client is running on is a special bot account. + /// + public static bool IsBotAccount { get; internal set; } + + /// + /// The URL that the client websocket is currently connected to. + /// + public string CurrentGatewayURL { get; internal set; } + + /// + /// Any private information assosciated with the account (regular clients only) + /// + public DiscordUserInformation ClientPrivateInformation { get; set; } + + /// + /// Custom properties containing parameters such as + /// * OS + /// * Referrer + /// * Browser + /// Used by Discord internally for connection. + /// + public DiscordProperties DiscordProperties { get; set; } = new DiscordProperties(); + + /// + /// The current DiscordMember object assosciated with the account you're connected to. + /// + public DiscordMember Me { get; internal set; } + + /// + /// Returns the debug logger used to log various debug events. + /// + public Logger GetTextClientLogger { get; } = new Logger(); + + /// + /// Returns the last debug logger for when the voice client was last connected. + /// + public Logger GetLastVoiceClientLogger; + + /// + /// If true, the logger will log everything. + /// Everything. + /// + public bool EnableVerboseLogging { get; set; } = false; + + /// + /// The version of the gateway. + /// + public int DiscordGatewayVersion { get; set; } + + [Obsolete] + internal bool V4Testing { get; set; } = false; + + /// + /// V4 related things. Getting this means our session has been successfully initiated. + /// + private string SessionID; + + /// + /// The last sequence we received used for v4 heartbeat. + /// + private int Sequence; + + /// + /// Whether or not to send Opcode 6 (resume) upon a socket being closed. + /// + public bool Autoconnect { get; set; } = true; + + private IDiscordWebSocket ws; + private List ServersList { get; set; } + private readonly int? IdleSinceUnixTime = null; + + private static string UserAgentString = + $" (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version})"; + + private DiscordVoiceClient VoiceClient; + private CancellationTokenSource KeepAliveTaskTokenSource = new CancellationTokenSource(); + private CancellationToken KeepAliveTaskToken; + private Task KeepAliveTask; + private Thread VoiceThread; //yuck + private static string StrippedEmail = ""; + + /// + /// Testing. + /// + private readonly List RemovedMembers = new List(); + + /// + /// Whether or not to write the latest READY upon receiving it. + /// If this is true, the client will write the contents of the READY message to 'READY_LATEST.txt' + /// If your client is connected to a lot of servers, this file will be quite large. + /// + public bool WriteLatestReady { get; set; } = false; + + /// + /// Whether or not to request all users in a guild (including offlines) on startup. + /// + public bool RequestAllUsersOnStartup { get; set; } = false; + + /// + /// A log of messages kept in a KeyValuePair. + /// The key is the id of the message, and the value is a DiscordMessage object. If you need raw json, this is contained + /// inside of the DiscordMessage object now. + /// + private readonly Dictionary MessageLog = new Dictionary(); + + //private List> MessageLog = new List>(); + private List PrivateChannels = new List(); + + /// + /// + /// If you have a token you wish to use, provide it here. Else, a login attempt will be made. + /// Set this to true if your bot is going to be a bot account + public DiscordClient(string tokenOverride = null, bool isBotAccount = false, bool enableLogging = true) + { + if (isBotAccount && tokenOverride == null) + throw new Exception("Token override cannot be null if using a bot account!"); + GetTextClientLogger.EnableLogging = enableLogging; + + token = tokenOverride; + IsBotAccount = isBotAccount; + + if (IsBotAccount) + UserAgentString = "DiscordBot " + UserAgentString; + else + UserAgentString = "Custom Discord Client " + UserAgentString; + + if (ClientPrivateInformation == null) + ClientPrivateInformation = new DiscordUserInformation(); + + GetTextClientLogger.LogMessageReceived += (sender, e) => + { + if (e.message.Level == MessageLevel.Error) + DisconnectFromVoice(); + if (TextClientDebugMessageReceived != null) + TextClientDebugMessageReceived(this, e); + }; + } + + /// + /// Current DiscordServers you're connected to. + /// + /// DiscordServer list of servers you're currently connected to. + public List GetServersList() + { + return ServersList; + } + + /// + /// Any messages logged since connection to the websocket. + /// + /// A KeyValuePair list of string-DiscordMessage. Where string is the message's ID + public Dictionary GetMessageLog() + { + return MessageLog; + } + + /// + /// Private channels assosciated with the account. + /// + /// a list of DiscordPrivateChannels. + public List GetPrivateChannels() + { + return PrivateChannels; + } + + /// + /// + /// True if connected to voice. + public bool ConnectedToVoice() + { + return VoiceClient != null ? VoiceClient.Connected : false; + } + + //eh + private void GetChannelsList(JObject m) + { + if (ServersList == null) + ServersList = new List(); + foreach (var j in m["d"]["guilds"]) + { + if (!j["unavailable"].IsNullOrEmpty() && j["unavailable"].ToObject()) + continue; //unavailable server + var temp = new DiscordServer(); + temp.parentclient = this; + temp.JoinedAt = j["joined_at"].ToObject(); + temp.ID = j["id"].ToString(); + temp.Name = j["name"].ToString(); + if (!j["icon"].IsNullOrEmpty()) + temp.icon = j["icon"].ToString(); + else + temp.icon = null; + + //temp.owner_id = j["owner_id"].ToString(); + var tempSubs = new List(); + + var tempRoles = new List(); + foreach (var u in j["roles"]) + { + var t = new DiscordRole + { + Color = new Color(u["color"].ToObject().ToString("x")), + Name = u["name"].ToString(), + Permissions = new DiscordPermission(u["permissions"].ToObject()), + Position = u["position"].ToObject(), + Managed = u["managed"].ToObject(), + ID = u["id"].ToString(), + Hoist = u["hoist"].ToObject() + }; + tempRoles.Add(t); + } + temp.Roles = tempRoles; + foreach (var u in j["channels"]) + { + var tempSub = new DiscordChannel(); + tempSub.Client = this; + tempSub.ID = u["id"].ToString(); + tempSub.Name = u["name"].ToString(); + tempSub.Type = u["type"].ToObject(); + if (!u["topic"].IsNullOrEmpty()) + tempSub.Topic = u["topic"].ToString(); + if (tempSub.Type == ChannelType.Voice && !u["bitrate"].IsNullOrEmpty()) + tempSub.Bitrate = u["bitrate"].ToObject(); + tempSub.Parent = temp; + var permissionoverrides = new List(); + foreach (var o in u["permission_overwrites"]) + { + var dpo = + new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); + dpo.id = o["id"].ToString(); + + if (o["type"].ToString() == "member") + dpo.type = DiscordPermissionOverride.OverrideType.member; + else + dpo.type = DiscordPermissionOverride.OverrideType.role; + + permissionoverrides.Add(dpo); + } + tempSub.PermissionOverrides = permissionoverrides; + + tempSubs.Add(tempSub); + } + temp.Channels = tempSubs; + foreach (var mm in j["members"]) + { + var member = JsonConvert.DeserializeObject(mm["user"].ToString()); + member.parentclient = this; + member.Roles = new List(); + var rawRoles = JArray.Parse(mm["roles"].ToString()); + if (rawRoles.Count > 0) + foreach (var role in rawRoles.Children()) + member.Roles.Add(temp.Roles.Find(x => x.ID == role.Value())); + else + member.Roles.Add(temp.Roles.Find(x => x.Name == "@everyone")); + temp.AddMember(member); + } + if (!j["presences"].IsNullOrEmpty()) + foreach (var presence in j["presences"]) + { + var member = temp.GetMemberByKey(presence["user"]["id"].ToString()); + if (member != null) + { + member.SetPresence(presence["status"].ToString()); + if (!presence["game"].IsNullOrEmpty()) + { + member.CurrentGame = presence["game"]["name"].ToString(); + if (presence["game"]["type"].ToObject() == 1) + { + member.Streaming = true; + if (presence["game"]["url"].ToString() != null) + member.StreamURL = presence["game"]["url"].ToString(); + } + } + } + } + temp.Region = j["region"].ToString(); + temp.Owner = temp.GetMemberByKey(j["owner_id"].ToString()); + ServersList.Add(temp); + } + if (PrivateChannels == null) + PrivateChannels = new List(); + foreach (var privateChannel in m["d"]["private_channels"]) + { + var tempPrivate = JsonConvert.DeserializeObject(privateChannel.ToString()); + tempPrivate.Client = this; + tempPrivate.user_id = privateChannel["recipient"]["id"].ToString(); + var potentialServer = new DiscordServer(); + ServersList.ForEach(x => + { + if (x.GetMemberByKey(privateChannel["recipient"]["id"].ToString()) != null) + potentialServer = x; + }); + if (potentialServer.Owner != null) //should be a safe test..i hope + { + var recipient = potentialServer.GetMemberByKey(privateChannel["recipient"]["id"].ToString()); + if (recipient != null) + tempPrivate.Recipient = recipient; + else + GetTextClientLogger.Log("Recipient was null!!!!", MessageLevel.Critical); + } + else + { + GetTextClientLogger.Log( + "No potential server found for user's private channel null! This will probably fix itself.", + MessageLevel.Debug); + } + PrivateChannels.Add(tempPrivate); + } + } + + /// + /// Sends an http DELETE request to leave the server you send in this parameter. + /// + /// The DiscordServer object you want to leave. + public void LeaveServer(DiscordServer server) + { + LeaveServer(server.ID); + } + + /// + /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify. + /// + /// The DiscordServer object you want to delete. + public void DeleteServer(DiscordServer server) + { + DeleteServer(server.ID); + } + + /// + /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify. + /// + /// The server's ID you want to delete. + public void LeaveServer(string ServerID) + { + var url = //Endpoints.BaseAPI + Endpoints.Guilds + $"/{ServerID}"; + Endpoints.BaseAPI + Endpoints.Users + Endpoints.Me + Endpoints.Guilds + + $"/{ServerID}"; //old, left for lulz + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while leaving server ({ServerID}): {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify by ID. + /// + /// The server's ID you want to delete. + public void DeleteServer(string ServerID) + { + if (IsBotAccount) + throw new Exception("Bot accounts can't own servers!"); + + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{ServerID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while deleting server ({ServerID}): {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Sends a message to a channel, what else did you expect? + /// + /// The text to send + /// DiscordChannel object to send the message to. + /// A DiscordMessage object of the message sent to Discord. + public DiscordMessage SendMessageToChannel(string message, DiscordChannel channel) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; + try + { + var result = JObject.Parse(WebWrapper.Post(url, token, + JsonConvert.SerializeObject(Utils.GenerateMessage(message)))); + if (result["content"].IsNullOrEmpty()) + throw new InvalidOperationException( + "Request returned a blank message, you may not have permission to send messages yet!"); + + var m = new DiscordMessage + { + ID = result["id"].ToString(), + Attachments = result["attachments"].ToObject(), + Author = channel.Parent.GetMemberByKey(result["author"]["id"].ToString()), + channel = channel, + TypeOfChannelObject = channel.GetType(), + Content = result["content"].ToString(), + RawJson = result, + timestamp = result["timestamp"].ToObject() + }; + return m; + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Error ocurred while sending message to channel ({channel.Name}): {ex.Message}", + MessageLevel.Error); + } + return null; + } + + /// + /// Sends a file to the specified DiscordChannel with the given message. + /// + /// The channel to send the message to. + /// The message you want the file to have with it. + /// The path to the file you wish to send (be careful!) + public void AttachFile(DiscordChannel channel, string message, string pathToFile) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; + //WebWrapper.PostWithAttachment(url, message, pathToFile); + try + { + var uploadResult = JObject.Parse( + WebWrapper.HttpUploadFile(url, token, pathToFile, "file", "image/jpeg", null)); + + if (!string.IsNullOrEmpty(message)) + EditMessage(uploadResult["id"].ToString(), message, channel); + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Error ocurred while sending file ({pathToFile}) to {channel.Name}: {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Sends a file to the specified DiscordChannel with the given message. + /// + /// The channel to send the message to. + /// The message you want the file to have with it. + /// A stream object to send the bytes from. + public void AttachFile(DiscordChannel channel, string message, Stream stream) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; + //WebWrapper.PostWithAttachment(url, message, pathToFile); + try + { + var uploadResult = + JObject.Parse(WebWrapper.HttpUploadFile(url, token, stream, "file", "image/jpeg", null)); + + if (!string.IsNullOrEmpty(message)) + EditMessage(uploadResult["id"].ToString(), message, channel); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while sending file by stream to {channel.Name}: {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Changes the current client's avatar. + /// Any high resolution pictures are automatically downscaled and Discord will perform jpeg compression on them. + /// + /// The Bitmap object assosciated with the avatar you wish to upload. + public void ChangeClientAvatar(Bitmap image) + { + var base64 = Convert.ToBase64String(Utils.ImageToByteArray(image)); + var type = "image/jpeg;base64"; + var req = $"data:{type},{base64}"; + var usernameRequestJson = JsonConvert.SerializeObject(new + { + avatar = req, + email = ClientPrivateInformation.Email, + password = ClientPrivateInformation.Password, + username = ClientPrivateInformation.Username + }); + var url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; + try + { + WebWrapper.Patch(url, token, usernameRequestJson); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while changing client's avatar: {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Changes the icon assosciated with the guild. Discord will perform jpeg compression and this image is automatically + /// downscaled. + /// + /// The bitmap object associated + /// The guild of the icon you wish to change. + public void ChangeGuildIcon(Bitmap image, DiscordServer guild) + { + var resized = new Bitmap(image, 200, 200); + + var base64 = Convert.ToBase64String(Utils.ImageToByteArray(resized)); + var type = "image/jpeg;base64"; + var req = $"data:{type},{base64}"; + var guildjson = JsonConvert.SerializeObject(new { icon = req, name = guild.Name }); + var url = Endpoints.BaseAPI + Endpoints.Guilds + "/" + guild.ID; + try + { + var result = JObject.Parse(WebWrapper.Patch(url, token, guildjson)); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while changing guild {guild.Name}'s icon: {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Returns a List of DiscordMessages. + /// + /// The channel to return them from. + /// How many to return + /// Messages before this message ID. + /// Messages after this message ID. + /// A List of DiscordMessages that you can iterate through. + public List GetMessageHistory(DiscordChannelBase channel, int count, string idBefore = "", + string idAfter = "") + { + var request = "https://discordapp.com/api/channels/" + channel.ID + $"/messages?&limit={count}"; + if (!string.IsNullOrEmpty(idBefore)) + request += $"&before={idBefore}"; + if (string.IsNullOrEmpty(idAfter)) + request += $"&after={idAfter}"; + + JArray result = null; + + try + { + var res = WebWrapper.Get(request, token); + result = JArray.Parse(res); + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Error ocurred while getting message history for channel {channel.ID}: {ex.Message}", + MessageLevel.Error); + } + + if (result != null) + { + var messageList = new List(); + /// NOTE + /// For some reason, the d object is excluded from this. + foreach (var item in result.Children()) + messageList.Add(new DiscordMessage + { + ID = item["id"].ToString(), + channel = channel, + Attachments = item["attachments"].ToObject(), + TypeOfChannelObject = channel.GetType(), + Author = GetMemberFromChannel(channel, item["author"]["id"].ToString()), + Content = item["content"].ToString(), + RawJson = item.ToObject(), + timestamp = DateTime.Parse(item["timestamp"].ToString()) + }); + return messageList; + } + + return null; + } + + /// + /// Changes the channel topic assosciated with the Discord text channel. + /// + /// The new channel topic. + /// The channel you wish to change the topic for. + public void ChangeChannelTopic(string Channeltopic, DiscordChannel channel) + { + var topicChangeJson = JsonConvert.SerializeObject( + new + { + name = channel.Name, + topic = Channeltopic + }); + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}"; + try + { + var result = JObject.Parse(WebWrapper.Patch(url, token, topicChangeJson)); + ServersList.Find(x => x.Channels.Find(y => y.ID == channel.ID) != null) + .Channels.Find(x => x.ID == channel.ID) + .Topic = Channeltopic; + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Error ocurred while changing channel topic for channel {channel.Name}: {ex.Message}", + MessageLevel.Error); + } + } + + /* + public List GetRoles(DiscordServer server) + { + return null; + } + */ + + /// + /// Used for changing the client's email, password, username, etc. + /// + /// + public void ChangeClientInformation(DiscordUserInformation info) + { + string usernameRequestJson; + if (info.Password != ClientPrivateInformation.Password) + { + usernameRequestJson = JsonConvert.SerializeObject(new + { + email = info.Email, + new_password = info.Password, + password = ClientPrivateInformation.Password, + username = info.Username, + avatar = info.Avatar + }); + ClientPrivateInformation.Password = info.Password; + try + { + File.Delete("token_cache"); + GetTextClientLogger.Log("Deleted token_cache due to change of password."); + } + catch (Exception) + { + /*ignore*/ + } + } + else + { + usernameRequestJson = JsonConvert.SerializeObject(new + { + email = info.Email, + password = info.Password, + username = info.Username, + avatar = info.Avatar + }); + } + + var url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; + try + { + var result = JObject.Parse(WebWrapper.Patch(url, token, usernameRequestJson)); + foreach (var server in ServersList) + if (server.Members[Me.ID] != null) + server.Members[Me.ID].Username = info.Username; + Me.Username = info.Username; + Me.Email = info.Email; + Me.Avatar = info.Avatar; + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while changing client's information: {ex.Message}", + MessageLevel.Error); + } + } + + private void ChangeClientUsername(string newUsername) + { + var url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; + var usernameRequestJson = JsonConvert.SerializeObject(new + { + email = ClientPrivateInformation.Email, + password = ClientPrivateInformation.Password, + username = newUsername, + avatar = Me.Avatar + }); + try + { + var result = JObject.Parse(WebWrapper.Patch(url, token, usernameRequestJson)); + if (result != null) + { + foreach (var server in ServersList) + if (server.Members[Me.ID] != null) + server.Members[Me.ID].Username = newUsername; + Me.Username = newUsername; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while changing client's username: {ex.Message}", + MessageLevel.Error); + } + } + + /// + /// Sends a private message to the given user. + /// + /// The message text to send them. + /// The member you want to send this to. + /// + public DiscordMessage SendMessageToUser(string message, DiscordMember member) + { + var url = Endpoints.BaseAPI + Endpoints.Users + $"/{Me.ID}" + Endpoints.Channels; + var initMessage = "{\"recipient_id\":" + member.ID + "}"; + + try + { + var result = JObject.Parse(WebWrapper.Post(url, token, initMessage)); + if (result != null) + { + var recipient = ServersList.Find( + x => x.GetMemberByKey(result["recipient"]["id"].ToString()) != null) + .Members[result["recipient"]["id"].ToString()]; + return SendActualMessage(result["id"].ToString(), message, recipient); + } + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while sending message to user, step 1: {ex.Message}", + MessageLevel.Error); + } + + return null; + } + + private DiscordMessage SendActualMessage(string id, string message, DiscordMember recipient) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{id}" + Endpoints.Messages; + var toSend = Utils.GenerateMessage(message); + + try + { + var result = JObject.Parse(WebWrapper.Post(url, token, JsonConvert.SerializeObject(toSend))); + var d = JsonConvert.DeserializeObject(result.ToString()); + d.Recipient = recipient; + d.channel = PrivateChannels.Find(x => x.ID == result["channel_id"].ToString()); + d.TypeOfChannelObject = typeof(DiscordPrivateChannel); + d.Author = Me; + return d; + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while sending message to user, step 2: {ex.Message}", + MessageLevel.Error); + } + return null; + } + + /// + /// Gets the string value of the current game your bot is 'playing'. + /// + public string GetCurrentGame { get; private set; } = ""; + + /// + /// Returns true if the websocket is not null and is alive. + /// + public bool WebsocketAlive + { + get { return ws != null ? ws.IsAlive : false; } + } + + public bool ReadyComplete { get; private set; } + + #region Message Received Crap.. + + /// + /// Updates the bot's 'Currently playing' status to the following text. Pass in null if you want to remove this. + /// + /// The game's name. Old gameid lookup can be seen at: http://hastebin.com/azijiyaboc.json/ + /// + /// Whether or not you want your bot to appear as if it is streaming. True means it will show it's + /// streaming. + /// + /// The 'url' for the stream, if your bot is streaming. + public void UpdateCurrentGame(string gameName, bool streaming, string url = null) + { + string msg; + if (gameName.ToLower().Trim() != "") + { + msg = JsonConvert.SerializeObject( + new + { + op = 3, + d = new + { + idle_since = IdleSinceUnixTime == null ? (object)null : IdleSinceUnixTime, + game = new + { + name = gameName, + type = streaming ? 1 : 0, + url = url != null ? url : (object)null + } + } + }); + GetCurrentGame = gameName; + GetTextClientLogger.Log($"Updating client's current game as '{gameName}'"); + } + else + { + msg = JsonConvert.SerializeObject( + new + { + op = 3, + d = new + { + idle_since = IdleSinceUnixTime == null ? (object)null : IdleSinceUnixTime, + game = (object)null + } + }); + GetTextClientLogger.Log("Setting current game to null."); + } + ws.Send(msg); + } + + /// + /// Updates the bot's status. + /// + /// True if you want the bot to report as idle. + public void UpdateBotStatus(bool idle) + { + string msg; + msg = JsonConvert.SerializeObject( + new + { + op = 3, + d = new + { + idle_since = idle ? (int)(DateTime.UtcNow - epoch).TotalMilliseconds : (object)null, + game = GetCurrentGame.ToLower().Trim() == "" ? (object)null : new { name = GetCurrentGame } + } + }); + ws.Send(msg); //let's try it! + } + + /// + /// Deletes a message with a specified ID. + /// This method will only work if the message was sent since the bot has ran. + /// + /// + public void DeleteMessage(string id) + { + var message = MessageLog[id]; + if (message != null) + SendDeleteRequest(message); + } + + /// + /// Deletes a specified DiscordMessage. + /// + /// + public void DeleteMessage(DiscordMessage message) + { + SendDeleteRequest(message); + } + + //public void DeletePrivateMessage(DiscordMessage message) + //{ + // SendDeleteRequest(message, true); + //} + + /// + /// Deletes all messages made by the bot since running. + /// + /// A count of messages deleted. + public int DeleteAllMessages() + { + var count = 0; + + foreach (var kvp in MessageLog) + if (kvp.Value.Author.ID == Me.ID) + { + SendDeleteRequest(kvp.Value); + count++; + } + return count; + } + + /// + /// Deletes the specified number of messages in a given channel. + /// Thank you to Siegen for this idea/method! + /// + /// The channel to delete messages in. + /// The amount of messages to delete (max 100) + /// The count of messages deleted. + public int DeleteMultipleMessagesInChannel(DiscordChannel channel, int count) + { + if (count > 100) + count = 100; + + var __count = 0; + + var messages = GetMessageHistory(channel, count, null, null); + + messages.ForEach(x => + { + if (x.channel.ID == channel.ID) + { + SendDeleteRequest(x); + __count++; + } + }); + + return __count; + } + + /// + /// + /// + /// + /// + /// + public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string username, bool caseSensitive) + { + if (string.IsNullOrEmpty(username)) + throw new ArgumentException("Argument given for username was null/empty."); + if (channel != null) + { + if (channel.GetType() == typeof(DiscordChannel)) //regular channel + { + var foundMember = ((DiscordChannel)channel).Parent.GetMemberByUsername(username, caseSensitive); + if (foundMember != null) + return foundMember; + GetTextClientLogger.Log("Error in GetMemberFromChannel: foundMember was null!", MessageLevel.Error); + } + else if (channel.GetType() == typeof(DiscordPrivateChannel)) + { + return ((DiscordPrivateChannel)channel).Recipient; + } + } + else + { + GetTextClientLogger.Log("Error in GetMemberFromChannel: channel was null!", MessageLevel.Error); + } + return null; + } + + /// + /// + /// + /// + /// + public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string id) + { + if (channel != null) + { + if (channel.GetType() == typeof(DiscordChannel)) //regular + { + var foundMember = ((DiscordChannel)channel).Parent.GetMemberByKey(id); + if (foundMember != null) + return foundMember; + GetTextClientLogger.Log($"Error in GetMemberFromChannel: foundMember was null! ID: {id}", + MessageLevel.Error); + } + else if (channel.GetType() == typeof(DiscordPrivateChannel)) + { + return ((DiscordPrivateChannel)channel).Recipient; + } + } + else + { + GetTextClientLogger.Log("Error in GetMemberFromChannel: channel was null!", MessageLevel.Error); + } + return null; + } + + /// + /// you probably shouldn't use this. + /// + /// + /// + public DiscordChannel GetChannelByName(string channelName) + { + try + { + return ServersList.Find(x => x.Channels.Find(y => y.Name.ToLower() == channelName.ToLower()) != null) + .Channels.Find(x => x.Name.ToLower() == channelName.ToLower()); + } + catch + { + return null; + } + } + + /// + /// + /// + /// + public DiscordChannel GetChannelByID(long id) + { + return ServersList.Find(x => x.Channels.Find(y => y.ID == id.ToString()) != null) + .Channels.Find(z => z.ID == id.ToString()); + } + + /// + /// (Client account only) accepts an invite to a server. + /// + /// The ID of the invite you want to accept. This is NOT the full URL of the invite + public void AcceptInvite(string inviteID) + { + if (!IsBotAccount) + { + if (inviteID.StartsWith("http://")) + inviteID = inviteID.Substring(inviteID.LastIndexOf('/') + 1); + + var url = Endpoints.BaseAPI + Endpoints.Invite + $"/{inviteID}"; + try + { + var result = WebWrapper.Post(url, token, "", true); + GetTextClientLogger.Log("Accept invite result: " + result); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error accepting invite: {ex.Message}", MessageLevel.Error); + } + } + else + { + throw new InvalidOperationException( + "Bot accounts can't accept invites normally! Please use the OAuth flow to add bots to servers you have the \"Manage Server\" permission in."); + } + } + + /// + /// + /// The last DiscordMessage sent + public DiscordMessage GetLastMessageSent() + { + foreach (var message in MessageLog) + if (message.Value.Author.ID == Me.ID) + return message.Value; + return null; + } + + /// + /// + /// + /// The last DiscordMessage sent in the given channel + public DiscordMessage GetLastMessageSent(DiscordChannel inChannel) + { + foreach (var message in MessageLog) + if (message.Value.Author.ID == Me.ID && message.Value.channel.ID == inChannel.ID) + return message.Value; + return null; + } + + /// + /// If you screwed up, you can use this method to edit a given message. This sends out an http patch request with a + /// replacement message + /// + /// The ID of the message you want to edit. + /// What you want the text to be edited to. + /// The channel the message is in + /// the new and improved DiscordMessage object. + public DiscordMessage EditMessage(string MessageID, string replacementMessage, DiscordChannel channel) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages + $"/{MessageID}"; + try + { + var replacement = JsonConvert.SerializeObject( + new + { + content = replacementMessage, + mentions = new string[0] + } + ); + var result = JObject.Parse(WebWrapper.Patch(url, token, replacement)); + + var m = new DiscordMessage + { + RawJson = result, + Attachments = result["attachments"].ToObject(), + Author = channel.Parent.GetMemberByKey(result["author"]["id"].ToString()), + TypeOfChannelObject = channel.GetType(), + channel = channel, + Content = result["content"].ToString(), + ID = result["id"].ToString(), + timestamp = result["timestamp"].ToObject() + }; + return m; + } + catch (Exception ex) + { + GetTextClientLogger.Log("Exception ocurred while editing: " + ex.Message, MessageLevel.Error); + } + + return null; + } + + private void SendDeleteRequest(DiscordMessage message) + { + string url; + //if(!user) + url = Endpoints.BaseAPI + Endpoints.Channels + $"/{message.channel.ID}" + Endpoints.Messages + + $"/{message.ID}"; + //else + //url = Endpoints.BaseAPI + Endpoints.Channels + $"/{message.channel.id}" + Endpoints.Messages + $"/{message.id}"; + try + { + var result = WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Exception ocurred while deleting message (ID: {message.ID}): " + ex.Message, + MessageLevel.Error); + } + } + + private DiscordMessage FindInMessageLog(string id) + { + foreach (var message in MessageLog) + if (message.Key == id) + return message.Value; + + return null; + } + + private DiscordChannel GetDiscordChannelByID(string id) + { + var returnVal = new DiscordChannel { ID = "-1" }; + ServersList.ForEach(x => + { + x.Channels.ForEach(y => + { + if (y.ID == id) + returnVal = y; + }); + }); + if (returnVal.ID != "-1") + return returnVal; + return null; + } + + #endregion Message Received Crap.. + + private string GetGatewayUrl() + { + if (token == null) + throw new NullReferenceException("token was null!"); + + //i'm ashamed of myself for this but i'm tired + tryAgain: + var url = Endpoints.BaseAPI + Endpoints.Gateway; + if (V4Testing) + url = "https://ptb.discordapp.com/api/gateway"; + try + { + var gateway = JObject.Parse(WebWrapper.Get(url, token))["url"].ToString(); + if (!string.IsNullOrEmpty(gateway)) + return gateway + (V4Testing ? "?encoding=json&v=4" : ""); + throw new NullReferenceException("Failed to retrieve Gateway urL!"); + } + catch (UnauthorizedAccessException) //bad token + { + GetTextClientLogger.Log("Got 401 from Discord. Token bad, deleting and retrying login..."); + if (File.Exists((uint)StrippedEmail.GetHashCode() + ".cache")) + File.Delete((uint)StrippedEmail.GetHashCode() + ".cache"); + SendLoginRequest(); + goto tryAgain; + } + catch (Exception ex) + { + GetTextClientLogger.Log("Exception ocurred while retrieving Gateway URL: " + ex.Message, + MessageLevel.Error); + return null; + } + } + + /// + /// + /// + /// + public DiscordServer GetServerChannelIsIn(DiscordChannel channel) + { + return ServersList.Find(x => x.Channels.Find(y => y.ID == channel.ID) != null); + } + + /// + /// Deletes a specified Discord channel given you have the permission. + /// + /// The DiscordChannel object to delete + public void DeleteChannel(DiscordChannel channel) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log("Exception ocurred while deleting channel: " + ex.Message, MessageLevel.Error); + } + } + + /// + /// Creates either a text or voice channel in a DiscordServer given a name. Given you have the permission of course. + /// + /// The server to create the channel in. + /// The name of the channel (will automatically be lowercased if text) + /// True if you want the channel to be a voice channel. + /// The newly created DiscordChannel + public DiscordChannel CreateChannel(DiscordServer server, string ChannelName, bool voice) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{server.ID}" + Endpoints.Channels; + var reqJson = JsonConvert.SerializeObject(new { name = ChannelName, type = voice ? "voice" : "text" }); + try + { + var result = JObject.Parse(WebWrapper.Post(url, token, reqJson)); + if (result != null) + { + var dc = new DiscordChannel + { + Client = this, + Name = result["name"].ToString(), + ID = result["id"].ToString(), + Type = result["type"].ToObject(), + Private = result["is_private"].ToObject() + }; + if (!result["topic"].IsNullOrEmpty()) + dc.Topic = result["topic"].ToString(); + if (dc.Type == ChannelType.Voice && !result["bitrate"].IsNullOrEmpty()) + dc.Bitrate = result["bitrate"].ToObject(); + + server.Channels.Add(dc); + return dc; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log("Exception ocurred while creating channel: " + ex.Message, MessageLevel.Error); + } + return null; + } + + /// + /// Creates an empty guild with only this client in it given the following name. + /// Unknown if works on bot accounts or not. + /// + /// The name of the guild you wish to create. + /// the created DiscordServer + public DiscordServer CreateGuild(string GuildName) + { + var createGuildUrl = Endpoints.BaseAPI + Endpoints.Guilds; + var req = JsonConvert.SerializeObject(new { name = GuildName }); + + try + { + var response = JObject.Parse(WebWrapper.Post(createGuildUrl, token, req)); + if (response != null) + { + var server = new DiscordServer(); + server.JoinedAt = response["joined_at"].ToObject(); + server.ID = response["id"].ToString(); + server.Name = response["name"].ToString(); + server.parentclient = this; + + var channelGuildUrl = createGuildUrl + $"/{server.ID}" + Endpoints.Channels; + var channelRespone = JArray.Parse(WebWrapper.Get(channelGuildUrl, token)); + foreach (var item in channelRespone.Children()) + server.Channels.Add(new DiscordChannel + { + Client = this, + Name = item["name"].ToString(), + ID = item["id"].ToString(), + Topic = item["topic"].ToString(), + Private = item["is_private"].ToObject(), + Type = item["type"].ToObject() + }); + + server.AddMember(Me); + server.Owner = server.GetMemberByKey(response["owner_id"].ToString()); + if (server.Owner == null) + GetTextClientLogger.Log("Owner is null in CreateGuild!", MessageLevel.Critical); + + ServersList.Add(server); + return server; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log("Exception ocurred while creating guild: " + ex.Message, MessageLevel.Error); + } + return null; + } + + /// + /// Edits the name of the guild, given you have the permission. + /// + /// The guild's name you wish to edit. + /// The new guild name. + public void EditGuildName(DiscordServer guild, string NewGuildName) + { + var editGuildUrl = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}"; + var newNameJson = JsonConvert.SerializeObject(new { name = NewGuildName }); + try + { + WebWrapper.Patch(editGuildUrl, token, newNameJson); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Exception ocurred while editing guild ({guild.Name}) name: " + ex.Message, + MessageLevel.Error); + } + } + + /// + /// Assigns a specified role to a member, given you have the permission. + /// + /// The guild you and the user are in. + /// The role you wish to assign them. + /// The member you wish to assign the role to. + public void AssignRoleToMember(DiscordServer guild, DiscordRole role, DiscordMember member) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Members + $"/{member.ID}"; + var message = JsonConvert.SerializeObject(new { roles = new[] { role.ID } }); + try + { + WebWrapper.Patch(url, token, message); + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Exception ocurred while assigning role ({role.Name}) to member ({member.Username}): " + + ex.Message, MessageLevel.Error); + } + } + + /// + /// Assigns the specified roles to a member, given you have the permission. + /// + /// The guild you and the user are in. + /// The roles you wish to assign them. + /// The member you wish to assign the role to. + public void AssignRoleToMember(DiscordServer guild, List roles, DiscordMember member) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Members + $"/{member.ID}"; + var rolesAsIds = new List(); + roles.ForEach(x => rolesAsIds.Add(x.ID)); + var message = JsonConvert.SerializeObject(new { roles = rolesAsIds.ToArray() }); + try + { + WebWrapper.Patch(url, token, message); + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Exception ocurred while assigning {roles.Count} role(s) to member ({member.Username}): " + + ex.Message, MessageLevel.Error); + } + } + + /// + /// Creates and invite to the given channel. + /// + /// + /// The invite's id. + public string CreateInvite(DiscordChannel channel) + { + var url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Invites; + try + { + var resopnse = JObject.Parse(WebWrapper.Post(url, token, "{\"validate\":\"\"}")); + if (resopnse != null) + return resopnse["code"].ToString(); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while creating invite for channel {channel.Name}: {ex.Message}", + MessageLevel.Error); + } + return null; + } + + /// + /// Deletes an invite by id + /// + /// The ID of the invite you wish to delete. + public void DeleteInvite(string id) + { + var url = Endpoints.BaseAPI + Endpoints.Invites + $"/{id}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while deleting invite: {ex.Message}", MessageLevel.Error); + } + } + + /// + /// Just prepends https://discord.gg/ to a given invite :) + /// + /// + /// A full invite URL. + public string MakeInviteURLFromCode(string id) + { + return "https://discord.gg/" + id; + } + + /// + /// Runs the websocket connection for the client hooking up the appropriate events. + /// + /// + /// If true, DiscordSharp will connect using the .Net Framework's built-in WebSocketClasses. + /// Please do not use this on Mono or versions of Windows below 8/8.1 + /// + public void Connect(bool useDotNetWebsocket = false) + { + CurrentGatewayURL = GetGatewayUrl(); + if (string.IsNullOrEmpty(CurrentGatewayURL)) + { + GetTextClientLogger.Log("Gateway URL was null or empty?!", MessageLevel.Critical); + return; + } + GetTextClientLogger.Log("Gateway retrieved: " + CurrentGatewayURL); + + if (useDotNetWebsocket) + { + ws = new NetWebSocket(CurrentGatewayURL); + GetTextClientLogger.Log("Using the built-in .Net websocket.."); + } + else + { + ws = new WebSocketSharpSocket(CurrentGatewayURL); + GetTextClientLogger.Log("Using WebSocketSharp websocket.."); + } + + ws.MessageReceived += (sender, e) => + { + var message = new JObject(); + try + { + message = JObject.Parse(e.Message); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"MessageReceived Error: {ex.Message}\n\n```{e.Message}\n```\n", + MessageLevel.Error); + } + + if (EnableVerboseLogging) + if (message["t"].ToString() != "READY") + GetTextClientLogger.Log(message.ToString(), MessageLevel.Unecessary); + + if (!message["t"].IsNullOrEmpty()) //contains a t parameter used for client events. + ClientPacketReceived(message); + else + MiscellaneousOpcodes(message); + + if (!message["s"].IsNullOrEmpty()) + Sequence = message["s"].ToObject(); + }; + ws.SocketOpened += (sender, e) => + { + SendIdentifyPacket(); + SocketOpened?.Invoke(this, null); + }; + ws.SocketClosed += (sender, e) => + { + var scev = new DiscordSocketClosedEventArgs(); + scev.Code = e.Code; + scev.Reason = e.Reason; + scev.WasClean = e.WasClean; + SocketClosed?.Invoke(this, scev); + + if (Autoconnect && !e.WasClean) + PerformReconnection(); + }; + ws.Connect(); + GetTextClientLogger.Log("Connecting.."); + } + + private void MiscellaneousOpcodes(JObject message) + { + switch (message["d"].ToObject()) + { + case Opcodes.INVALIDATE_SESSION: + // TODO: the session was invalidated and a full reconnection must be performed. + GetTextClientLogger.Log($"The session was invalidated. ", MessageLevel.Critical); + break; + } + } + + private void PerformReconnection() + { + var resumeJson = JsonConvert.SerializeObject(new + { + op = Opcodes.RESUME, + d = new + { + seq = Sequence, + token, + session_id = SessionID + } + }); + } + + private void SendIdentifyPacket() + { + var initJson = JsonConvert.SerializeObject(new + { + op = 2, + d = new + { + v = 4, + token, + /*large_threshold = 50,*/ + properties = DiscordProperties + } + }); + + GetTextClientLogger.Log("Sending initJson ( " + initJson + " )"); + + ws.Send(initJson); + } + + private void BeginHeartbeatTask() + { + KeepAliveTaskTokenSource = new CancellationTokenSource(); + KeepAliveTaskToken = KeepAliveTaskTokenSource.Token; + KeepAliveTask = new Task(() => + { + while (true) + { + GetTextClientLogger.Log("Hello from inside KeepAliveTask!"); + Thread.Sleep(HeartbeatInterval); + KeepAlive(); + } + }, KeepAliveTaskToken); + KeepAliveTask.Start(); + GetTextClientLogger.Log("Began keepalive task.."); + } + +#if NETFX4_5 + + private void ConnectToVoiceAsync() + { + VoiceClient.InitializeOpusEncoder(); + VoiceThread = new Thread(() => VoiceClient.Initiate()); + VoiceThread.Start(); + } + +#else + private Task ConnectToVoiceAsync() + { + VoiceClient.InitializeOpusEncoder(); + return Task.Factory.StartNew(() => VoiceClient.Initiate()); + } +#endif + + /// + /// Kicks a specified DiscordMember from the guild that's assumed from their + /// parent property. + /// + /// + public void KickMember(DiscordMember member) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{member.Parent.ID}" + Endpoints.Members + + $"/{member.ID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error during KickMember\n\t{ex.Message}\n\t{ex.StackTrace}", + MessageLevel.Error); + } + } + + /// + /// Bans a specified DiscordMember from the guild that's assumed from their + /// parent property. + /// + /// + /// The number of days the user should be banned for, or 0 for infinite. + public DiscordMember BanMember(DiscordMember member, int days = 0) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{member.Parent.ID}" + Endpoints.Bans + $"/{member.ID}"; + url += $"?delete-message-days={days}"; + try + { + WebWrapper.Put(url, token); + return member; + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error during BanMember\n\t{ex.Message}\n\t{ex.StackTrace}", + MessageLevel.Error); + return null; + } + } + + /// + /// Bans a specified DiscordMember from the guild that's assumed from their + /// parent property. + /// + /// + /// + /// + /// + public DiscordMember BanMember(DiscordMember member, DiscordServer serverOverride, int days = 0) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{serverOverride.ID}" + Endpoints.Bans + $"/{member.ID}"; + url += $"?delete-message-days={days}"; + try + { + WebWrapper.Put(url, token); + return member; + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error during BanMember\n\t{ex.Message}\n\t{ex.StackTrace}", + MessageLevel.Error); + return null; + } + } + + /// + /// Retrieves a DiscordMember List of members banned in the specified server. + /// + /// + /// Null if no permission. + public List GetBans(DiscordServer server) + { + var returnVal = new List(); + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{server.ID}" + Endpoints.Bans; + try + { + var __res = WebWrapper.Get(url, token); + var permissionCheck = JObject.Parse(__res); + { + if (!permissionCheck["message"].IsNullOrEmpty()) + return null; //no permission + } + var response = JArray.Parse(__res); + if (response != null && response.Count > 0) + { + GetTextClientLogger.Log($"Ban count: {response.Count}"); + + foreach (var memberStub in response) + { + var temp = JsonConvert.DeserializeObject(memberStub["user"].ToString()); + if (temp != null) + returnVal.Add(temp); + else + GetTextClientLogger.Log( + $"memberStub[\"user\"] was null?! Username: {memberStub["user"]["username"]} ID: {memberStub["user"]["username"]}", + MessageLevel.Error); + } + } + else + { + return returnVal; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"An error ocurred while retrieving bans for server \"{server.Name}\"\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", + MessageLevel.Error); + } + return returnVal; + } + + /// + /// Removes a ban on the user. + /// + /// The guild to lift the ban from. + /// The ID of the user to lift the ban. + public void RemoveBan(DiscordServer guild, string userID) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Bans + $"/{userID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error during RemoveBan\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", + MessageLevel.Error); + } + } + + /// + /// Removes a ban on the user. + /// + /// The guild to lift the ban from. + /// The DiscordMember object of the user to lift the ban from, assuming you have it. + public void RemoveBan(DiscordServer guild, DiscordMember member) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Bans + $"/{member.ID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error during RemoveBan\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", + MessageLevel.Error); + } + } + + /// + /// Echoes a received audio packet back. + /// + /// + public void EchoPacket(DiscordAudioPacket packet) + { + if (VoiceClient != null && ConnectedToVoice()) + VoiceClient.EchoPacket(packet).Wait(); + } + + /// + /// Connects to a given voice channel. + /// + /// The channel to connect to. + /// The voice configuration to use. If null, default values will be used. + /// Whether or not the client will connect muted. Defaults to false. + /// Whether or not the client will connect deaf. Defaults to false. + public void ConnectToVoiceChannel(DiscordChannel channel, DiscordVoiceConfig voiceConfig = null, + bool clientMuted = false, bool clientDeaf = false) + { + if (channel.Type != ChannelType.Voice) + throw new InvalidOperationException($"Channel '{channel.ID}' is not a voice channel!"); + + if (ConnectedToVoice()) + DisconnectFromVoice(); + + if (VoiceClient == null) + if (voiceConfig == null) + VoiceClient = new DiscordVoiceClient(this, new DiscordVoiceConfig()); + else + VoiceClient = new DiscordVoiceClient(this, voiceConfig); + VoiceClient.Channel = channel; + VoiceClient.ErrorReceived += (sender, e) => + { + if (GetLastVoiceClientLogger != null) + { + GetLastVoiceClientLogger = VoiceClient.GetDebugLogger; + DisconnectFromVoice(); + } + }; + VoiceClient.UserSpeaking += (sender, e) => + { + if (UserSpeaking != null) + UserSpeaking(this, e); + }; + VoiceClient.VoiceConnectionComplete += (sender, e) => + { + if (VoiceClientConnected != null) + VoiceClientConnected(this, e); + }; + VoiceClient.QueueEmpty += (sender, e) => { VoiceQueueEmpty?.Invoke(this, e); }; + + var joinVoicePayload = JsonConvert.SerializeObject(new + { + op = 4, + d = new + { + guild_id = channel.Parent.ID, + channel_id = channel.ID, + self_mute = clientMuted, + self_deaf = clientDeaf + } + }); + + ws.Send(joinVoicePayload); + } + + /// + /// Clears the internal message log cache + /// + /// The number of internal messages cleared. + public int ClearInternalMessageLog() + { + var totalCount = MessageLog.Count; + MessageLog.Clear(); + return totalCount; + } + + /// + /// Iterates through a server's members and removes offline users. + /// + /// + /// The amount of users cleared. + public int ClearOfflineUsersFromServer(DiscordServer server) + { + return server.ClearOfflineMembers(); + } + + /// + /// Also disposes + /// + public void DisconnectFromVoice() + { + var disconnectMessage = JsonConvert.SerializeObject(new + { + op = 4, + d = new + { + guild_id = VoiceClient != null && VoiceClient.Channel != null + ? VoiceClient.Channel.Parent.ID + : (object)null, + channel_id = (object)null, + self_mute = true, + self_deaf = false + } + }); + if (VoiceClient != null) + try + { + VoiceClient.Dispose(); + VoiceClient = null; + + ws.Send(disconnectMessage); + } + catch + { + } + if (ws != null) + ws.Send(disconnectMessage); + VoiceClient = null; + if (VoiceThread != null) + VoiceThread.Abort(); + GetTextClientLogger.Log($"Disconnected from voice. VoiceClient null: {VoiceClient == null}"); + } + + /// + /// + /// The current VoiceClient or null. + public DiscordVoiceClient GetVoiceClient() + { + if (ConnectedToVoice() && VoiceClient != null) + return VoiceClient; + + return null; + } + + /// + /// Creates a default role in the specified guild. + /// + /// The guild to make the role in. + /// The newly created role. + public DiscordRole CreateRole(DiscordServer guild) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles; + + try + { + var result = JObject.Parse(WebWrapper.Post(url, token, "")); + + if (result != null) + { + var d = new DiscordRole + { + Color = new Color(result["color"].ToObject().ToString("x")), + Hoist = result["hoist"].ToObject(), + ID = result["id"].ToString(), + Managed = result["managed"].ToObject(), + Name = result["name"].ToString(), + Permissions = new DiscordPermission(result["permissions"].ToObject()), + Position = result["position"].ToObject() + }; + + ServersList.Find(x => x.ID == guild.ID).Roles.Add(d); + return d; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while creating role in guild {guild.Name}: {ex.Message}", + MessageLevel.Error); + } + return null; + } + + /// + /// Edits a role with the new role information. + /// + /// The guild the role is in. + /// the new role. + /// The newly edited role returned from Discord. + public DiscordRole EditRole(DiscordServer guild, DiscordRole newRole) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles + $"/{newRole.ID}"; + var request = JsonConvert.SerializeObject( + new + { + color = decimal.Parse(newRole.Color.ToDecimal().ToString()), + hoist = newRole.Hoist, + name = newRole.Name, + permissions = newRole.Permissions.GetRawPermissions() + } + ); + + try + { + var result = JObject.Parse(WebWrapper.Patch(url, token, request)); + if (result != null) + { + var d = new DiscordRole + { + Color = new Color(result["color"].ToObject().ToString("x")), + Hoist = result["hoist"].ToObject(), + ID = result["id"].ToString(), + Managed = result["managed"].ToObject(), + Name = result["name"].ToString(), + Permissions = new DiscordPermission(result["permissions"].ToObject()), + Position = result["position"].ToObject() + }; + + ServersList.Find(x => x.ID == guild.ID).Roles.Remove(d); + ServersList.Find(x => x.ID == guild.ID).Roles.Add(d); + return d; + } + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while editing role ({newRole.Name}): {ex.Message}", + MessageLevel.Error); + } + + return null; + } + + /// + /// Deletes a specified role. + /// + /// The guild the role is in. + /// The role to delete. + public void DeleteRole(DiscordServer guild, DiscordRole role) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles + $"/{role.ID}"; + try + { + WebWrapper.Delete(url, token); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Error ocurred while deleting role ({role.Name}): {ex.Message}", + MessageLevel.Error); + } + } + + private JObject ServerInfo(string channelOrServerId) + { + var url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{channelOrServerId}"; + try + { + return JObject.Parse(WebWrapper.Get(url, token)); + } + catch (Exception ex) + { + GetTextClientLogger.Log("(Private) Error ocurred while retrieving server info: " + ex.Message, + MessageLevel.Error); + } + return null; + } + + private int HeartbeatInterval = 41250; + private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private void KeepAlive() + { + //string keepAliveJson = "{\"op\":" + Opcodes.HEARTBEAT + ", \"d\":" + Sequence + "}"; + var keepAliveJson = JsonConvert.SerializeObject(new + { + op = Opcodes.HEARTBEAT, + d = Sequence + }); + if (ws != null) + { + ws.Send(keepAliveJson); + KeepAliveSent?.Invoke(this, + new DiscordKeepAliveSentEventArgs { SentAt = DateTime.Now, JsonSent = keepAliveJson }); + } + } + + /// + /// Disposes. + /// + public void Dispose() + { + try + { + KeepAliveTaskTokenSource.Cancel(); + ws.Close(); + ws = null; + ServersList = null; + PrivateChannels = null; + Me = null; + token = null; + ClientPrivateInformation = null; + } + catch + { + /*already been disposed elsewhere */ + } + } + + /// + /// Logs out of Discord and then disposes. + /// + public void Logout() + { + var url = Endpoints.BaseAPI + Endpoints.Auth + "/logout"; + var msg = JsonConvert.SerializeObject(new { token }); + WebWrapper.Post(url, msg); + Dispose(); + } + + /// + /// Sends a login request. + /// + /// The token if login was succesful, or null if not + public string SendLoginRequest() + { + if (token == null) //no token override provided, need to read token + { + if (string.IsNullOrEmpty(ClientPrivateInformation.Email)) + throw new ArgumentNullException("Email was null/invalid!"); + StrippedEmail = + ClientPrivateInformation.Email.Replace('@', '_') + .Replace('.', '_'); //strips characters from email for hashing + + if (File.Exists(StrippedEmail.GetHashCode() + ".cache")) + { + var read = ""; + using (var sr = new StreamReader(StrippedEmail.GetHashCode() + ".cache")) + { + read = sr.ReadLine(); + if (read.StartsWith("#")) //comment + { + token = sr.ReadLine(); + GetTextClientLogger.Log("Loading token from cache."); + } + token = token.Trim(); //trim any excess whitespace + } + } + else + { + if (ClientPrivateInformation == null || ClientPrivateInformation.Email == null || + ClientPrivateInformation.Password == null) + throw new ArgumentNullException("You didn't supply login information!"); + var url = Endpoints.BaseAPI + Endpoints.Auth + Endpoints.Login; + var msg = JsonConvert.SerializeObject(new + { + email = ClientPrivateInformation.Email, + password = ClientPrivateInformation.Password + }); + GetTextClientLogger.Log("No token present, sending login request.."); + var result = JObject.Parse(WebWrapper.Post(url, msg)); + + if (result["token"].IsNullOrEmpty()) + { + var message = "Failed to login to Discord."; + if (!result["email"].IsNullOrEmpty()) + message += " Email was invalid: " + result["email"]; + if (!result["password"].IsNullOrEmpty()) + message += " password was invalid: " + result["password"]; + + throw new DiscordLoginException(message); + } + token = result["token"].ToString(); + + using (var sw = new StreamWriter(StrippedEmail.GetHashCode() + ".cache")) + { + sw.WriteLine($"#Token cache for {ClientPrivateInformation.Email}"); + sw.WriteLine(token); + GetTextClientLogger.Log($"{StrippedEmail.GetHashCode()}.cache written!"); + } + } + } + return token; + } + } +} \ No newline at end of file diff --git a/DiscordSharp/Discord/DiscordClientEventHandlers.cs b/DiscordSharp/Discord/DiscordClientEventHandlers.cs new file mode 100644 index 0000000..d82a5a4 --- /dev/null +++ b/DiscordSharp/Discord/DiscordClientEventHandlers.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DiscordSharp.Events; + +namespace DiscordSharp.Discord +{ + public partial class DiscordClient + { + #region Event declaration + + public event EventHandler MessageReceived; + + public event EventHandler Connected; + + public event EventHandler SocketOpened; + + public event EventHandler SocketClosed; + + public event EventHandler ChannelCreated; + + public event EventHandler PrivateChannelCreated; + + public event EventHandler PrivateMessageReceived; + + public event EventHandler KeepAliveSent; + + public event EventHandler MentionReceived; + + public event EventHandler UserTypingStart; + + public event EventHandler MessageEdited; + + public event EventHandler PresenceUpdated; + + public event EventHandler URLMessageAutoUpdate; + + public event EventHandler VoiceStateUpdate; + + public event EventHandler UnknownMessageTypeReceived; + + public event EventHandler MessageDeleted; + + public event EventHandler UserUpdate; + + public event EventHandler UserAddedToServer; + + public event EventHandler UserRemovedFromServer; + + public event EventHandler GuildCreated; + + /// + /// Occurs when a guild becomes available after being unavailable. + /// + public event EventHandler GuildAvailable; + + public event EventHandler GuildDeleted; + + public event EventHandler ChannelUpdated; + + public event EventHandler TextClientDebugMessageReceived; + + public event EventHandler VoiceClientDebugMessageReceived; + + public event EventHandler ChannelDeleted; + + public event EventHandler GuildUpdated; + + public event EventHandler RoleDeleted; + + public event EventHandler RoleUpdated; + + public event EventHandler GuildMemberUpdated; + + public event EventHandler GuildMemberBanned; + + public event EventHandler PrivateChannelDeleted; + + public event EventHandler BanRemoved; + + public event EventHandler PrivateMessageDeleted; + + public event EventHandler PrivateMessageUpdate; + + #region Voice + + /// + /// For use when connected to voice only. + /// + public event EventHandler AudioPacketReceived; + + /// + /// For use when connected to voice only. + /// + public event EventHandler UserSpeaking; + + /// + /// For use when connected to voice only. + /// + public event EventHandler UserLeftVoiceChannel; + + /// + /// Occurs when the voice client is fully connected to voice. + /// + public event EventHandler VoiceClientConnected; + + /// + /// Occurs when the voice queue is emptied. + /// + public event EventHandler VoiceQueueEmpty; + + #endregion Voice + + #endregion Event declaration + } +} \ No newline at end of file diff --git a/DiscordSharp/Discord/DiscordClientPacketHandlers.cs b/DiscordSharp/Discord/DiscordClientPacketHandlers.cs new file mode 100644 index 0000000..bd3cc80 --- /dev/null +++ b/DiscordSharp/Discord/DiscordClientPacketHandlers.cs @@ -0,0 +1,1208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DiscordSharp.Events; +using DiscordSharp.Objects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DiscordSharp.Discord +{ + public partial class DiscordClient + { + private void ClientPacketReceived(JObject message) + { + switch (message["t"].ToString()) + { + case "READY": + Sequence = message["s"].ToObject(); + DiscordGatewayVersion = message["d"]["v"].ToObject(); + HeartbeatInterval = message["d"]["heartbeat_interval"].ToObject(); + BeginHeartbeatTask(); + if (WriteLatestReady) + using (var sw = new StreamWriter("READY_LATEST.txt")) + { + sw.Write(message); + } + Me = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); + Me.parentclient = this; + IsBotAccount = message["d"]["user"]["bot"].IsNullOrEmpty() + ? false + : message["d"]["user"]["bot"].ToObject(); + ClientPrivateInformation.Avatar = Me.Avatar; + ClientPrivateInformation.Username = Me.Username; + GetChannelsList(message); + SessionID = message["d"]["session_id"].ToString(); + + //TESTING + var guildID = new string[ServersList.Count]; + for (var i = 0; i < guildID.Length; i++) + guildID[i] = ServersList[i].ID; + + if (RequestAllUsersOnStartup) + { + var wsChunkTest = JsonConvert.SerializeObject(new + { + op = 8, + d = new + { + guild_id = guildID, + query = "", + limit = 0 + } + }); + ws.Send(wsChunkTest); + } + + ReadyComplete = true; + + Task.Run(async () => + { + await Task.Delay(3000); + Connected?.Invoke(this, new DiscordConnectEventArgs { User = Me }); + }); //fire and forget waiting of up to 3 seconds for guilds to become available. + break; + + case "GUILD_MEMBERS_CHUNK": + GuildMemberChunkEvents(message); + break; + + case "GUILD_MEMBER_REMOVE": + GuildMemberRemoveEvents(message); + break; + + case "GUILD_MEMBER_ADD": + GuildMemberAddEvents(message); + break; + + case "GUILD_DELETE": + GuildDeleteEvents(message); + break; + + case "GUILD_CREATE": + GuildCreateEvents(message); + break; + + case "GUILD_MEMBER_UPDATE": + GuildMemberUpdateEvents(message); + break; + + case "GUILD_UPDATE": + GuildUpdateEvents(message); + break; + + case "GUILD_ROLE_DELETE": + GuildRoleDeleteEvents(message); + break; + + case "GUILD_ROLE_UPDATE": + GuildRoleUpdateEvents(message); + break; + + case "PRESENCE_UPDATE": + PresenceUpdateEvents(message); + break; + + case "MESSAGE_UPDATE": + MessageUpdateEvents(message); + break; + + case "TYPING_START": + var server = ServersList.Find( + x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); + if (server != null) + { + var channel = server.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + var uuser = server.GetMemberByKey(message["d"]["user_id"].ToString()); + if (UserTypingStart != null) + UserTypingStart(this, + new DiscordTypingStartEventArgs + { + user = uuser, + Channel = channel, + Timestamp = int.Parse(message["d"]["timestamp"].ToString()) + }); + } + break; + + case "MESSAGE_CREATE": + MessageCreateEvents(message); + break; + + case "CHANNEL_CREATE": + ChannelCreateEvents(message); + break; + + case "VOICE_STATE_UPDATE": + VoiceStateUpdateEvents(message); + break; + + case "VOICE_SERVER_UPDATE": + VoiceServerUpdateEvents(message); + break; + + case "MESSAGE_DELETE": + MessageDeletedEvents(message); + break; + + case "USER_UPDATE": + UserUpdateEvents(message); + break; + + case "CHANNEL_UPDATE": + ChannelUpdateEvents(message); + break; + + case "CHANNEL_DELETE": + ChannelDeleteEvents(message); + break; + + case "GUILD_BAN_ADD": + GuildMemberBannedEvents(message); + break; + + case "GUILD_BAN_REMOVE": + GuildMemberBanRemovedEvents(message); + break; + + case "MESSAGE_ACK": //ignore this message, it's irrelevant + break; + + default: + if (UnknownMessageTypeReceived != null) + UnknownMessageTypeReceived(this, new UnknownMessageEventArgs { RawJson = message }); + break; + } + } + + #region GuildMember + + private void GuildMemberAddEvents(JObject message) + { + DiscordGuildMemberAddEventArgs e = new DiscordGuildMemberAddEventArgs(); + e.RawJson = message; + e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + + DiscordMember existingMember = e.Guild.GetMemberByKey(message["d"]["user"]["id"].ToString()); + if (existingMember != null) + { + DiscordMember newMember = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); + newMember.parentclient = this; + e.AddedMember = newMember; + newMember.Parent = e.Guild; + e.Roles = message["d"]["roles"].ToObject(); + e.JoinedAt = DateTime.Parse(message["d"]["joined_at"].ToString()); + + ServersList.Find(x => x == e.Guild).AddMember(newMember); + if (UserAddedToServer != null) + UserAddedToServer(this, e); + } + else + { + GetTextClientLogger.Log($"Skipping guild member add because user already exists in server.", MessageLevel.Debug); + } + } + + private void GuildMemberUpdateEvents(JObject message) + { + DiscordServer server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + + DiscordMember memberUpdated = server.GetMemberByKey(message["d"]["user"]["id"].ToString()); + if (memberUpdated != null) + { + memberUpdated.Username = message["d"]["user"]["username"].ToString(); + if (message["d"]["nick"] != null) + { + if (message["d"]["nick"].ToString() == null) + memberUpdated.Nickname = ""; //No nickname + else + memberUpdated.Nickname = message["d"]["nick"].ToString(); + } + + if (!message["d"]["user"]["avatar"].IsNullOrEmpty()) + memberUpdated.Avatar = message["d"]["user"]["avatar"].ToString(); + memberUpdated.Discriminator = message["d"]["user"]["discriminator"].ToString(); + memberUpdated.ID = message["d"]["user"]["id"].ToString(); + + foreach (var roles in message["d"]["roles"]) + { + memberUpdated.Roles.Add(server.Roles.Find(x => x.ID == roles.ToString())); + } + + server.AddMember(memberUpdated); + GuildMemberUpdated?.Invoke(this, new DiscordGuildMemberUpdateEventArgs { MemberUpdate = memberUpdated, RawJson = message, ServerUpdated = server }); + } + else + { + GetTextClientLogger.Log("memberUpdated was null?!?!?!", MessageLevel.Debug); + } + } + + private void GuildMemberChunkEvents(JObject message) + { + if (!message["d"]["members"].IsNullOrEmpty()) + { + DiscordServer inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + JArray membersAsArray = JArray.Parse(message["d"]["members"].ToString()); + foreach (var member in membersAsArray) + { + //if (GuildHasMemberWithID(inServer, member["user"]["id"].ToString())) + // continue; + DiscordMember _member = JsonConvert.DeserializeObject(member["user"].ToString()); + if (!member["user"]["roles"].IsNullOrEmpty()) + { + JArray rollsArray = JArray.Parse(member["user"]["roles"].ToString()); + if (rollsArray.Count > 0) + { + foreach (var rollID in rollsArray) + _member.Roles.Add(inServer.Roles.Find(x => x.ID == rollID.ToString())); + } + } + _member.Muted = member["mute"].ToObject(); + _member.Deaf = member["deaf"].ToObject(); + _member.Roles.Add(inServer.Roles.Find(x => x.Name == "@everyone")); + _member.Status = Status.Offline; + _member.parentclient = this; + _member.Parent = inServer; + inServer.AddMember(_member); + + ///Check private channels + DiscordPrivateChannel _channel = PrivateChannels.Find(x => x.user_id == _member.ID); + if (_channel != null) + { + GetTextClientLogger.Log("Found user for private channel!", MessageLevel.Debug); + _channel.Recipient = _member; + } + } + } + } + + private void GuildMemberRemoveEvents(JObject message) + { + DiscordGuildMemberRemovedEventArgs e = new DiscordGuildMemberRemovedEventArgs(); + DiscordMember removed = new DiscordMember(this); + removed.parentclient = this; + + List membersToRemove = new List(); + foreach (var server in ServersList) + { + if (server.ID != message["d"]["guild_id"].ToString()) + continue; + foreach (var member in server.Members) + { + if (member.Value.ID == message["d"]["user"]["id"].ToString()) + { + removed = member.Value; + membersToRemove.Add(removed); + RemovedMembers.Add(removed); + } + } + } + + foreach (var member in membersToRemove) + { + foreach (var server in ServersList) + { + try + { + server.RemoveMember(member.ID); + } + catch { } //oh, you mean useless? + } + } + e.MemberRemoved = removed; + e.Server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + e.RawJson = message; + + if (UserRemovedFromServer != null) + UserRemovedFromServer(this, e); + } + + private void GuildMemberBannedEvents(JObject message) + { + DiscordGuildBanEventArgs e = new DiscordGuildBanEventArgs(); + e.Server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + if (e.Server != null) + { + e.MemberBanned = e.Server.GetMemberByKey(message["d"]["user"]["id"].ToString()); + if (e.MemberBanned != null) + { + if (GuildMemberBanned != null) + GuildMemberBanned(this, e); + ServersList.Find(x => x.ID == e.Server.ID).RemoveMember(e.MemberBanned.ID); + } + else + { + GetTextClientLogger.Log("Error in GuildMemberBannedEvents: MemberBanned is null, attempting internal index of removed members.", MessageLevel.Error); + e.MemberBanned = RemovedMembers.Find(x => x.ID == message["d"]["user"]["id"].ToString()); + if (e.MemberBanned != null) + { + if (GuildMemberBanned != null) + GuildMemberBanned(this, e); + } + else + { + GetTextClientLogger.Log("Error in GuildMemberBannedEvents: MemberBanned is null, not even found in internal index!", MessageLevel.Error); + } + } + } + else + { + GetTextClientLogger.Log("Error in GuildMemberBannedEvents: Server is null?!", MessageLevel.Error); + } + } + + private void GuildMemberBanRemovedEvents(JObject message) + { + DiscordBanRemovedEventArgs e = new DiscordBanRemovedEventArgs(); + + e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + e.MemberStub = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); + + if (BanRemoved != null) + BanRemoved(this, e); + } + + #endregion GuildMember + + private void VoiceServerUpdateEvents(JObject message) + { + // TODO VoiceClient is null when disconnecting from voice + if (VoiceClient == null) + { + return; + } + VoiceClient.VoiceEndpoint = message["d"]["endpoint"].ToString(); + VoiceClient.Token = message["d"]["token"].ToString(); + + VoiceClient.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + VoiceClient.Me = Me; + + VoiceClient.PacketReceived += (sender, e) => + { + AudioPacketReceived?.Invoke(sender, e); + }; + + VoiceClient.DebugMessageReceived += (sender, e) => + { + if (VoiceClientDebugMessageReceived != null) + VoiceClientDebugMessageReceived(this, e); + }; + + ConnectToVoiceAsync(); + } + + private void VoiceStateUpdateEvents(JObject message) + { + var f = message["d"]["channel_id"]; + if (f.ToString() == null) + { + var le = new DiscordLeftVoiceChannelEventArgs(); + var inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + le.User = inServer.GetMemberByKey(message["d"]["user_id"].ToString()); + le.Guild = inServer; + le.RawJson = message; + + if (VoiceClient != null && VoiceClient.Connected) + VoiceClient.MemberRemoved(le.User); + if (UserLeftVoiceChannel != null) + UserLeftVoiceChannel(this, le); + return; + } + var e = new DiscordVoiceStateUpdateEventArgs(); + e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + var memberToUpdate = e.Guild.GetMemberByKey(message["d"]["user_id"].ToString()); + if (memberToUpdate != null) + { + e.Channel = e.Guild.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + memberToUpdate.CurrentVoiceChannel = e.Channel; + if (!message["d"]["self_deaf"].IsNullOrEmpty()) + e.SelfDeaf = message["d"]["self_deaf"].ToObject(); + e.Deaf = message["d"]["deaf"].ToObject(); + if (!message["d"]["self_mute"].IsNullOrEmpty()) + e.SelfMute = message["d"]["self_mute"].ToObject(); + e.Mute = message["d"]["mute"].ToObject(); + memberToUpdate.Muted = e.Mute; + e.Suppress = message["d"]["suppress"].ToObject(); + memberToUpdate.Deaf = e.Suppress; + e.RawJson = message; + + e.User = memberToUpdate; + + if (VoiceClient != null && VoiceClient.Connected) + VoiceClient.MemberAdded(e.User); + + if (!message["d"]["session_id"].IsNullOrEmpty()) //then this has to do with you + if (e.User.ID == Me.ID) + { + Me.Muted = e.SelfMute; + Me.Deaf = e.SelfDeaf; + if (VoiceClient != null) + VoiceClient.SessionID = message["d"]["session_id"].ToString(); + } + + if (VoiceStateUpdate != null) + VoiceStateUpdate(this, e); + } + } + + private void GuildCreateEvents(JObject message) + { + var e = new DiscordGuildCreateEventArgs(); + e.RawJson = message; + var server = new DiscordServer(); + server.JoinedAt = message["d"]["joined_at"].ToObject(); + server.parentclient = this; + server.ID = message["d"]["id"].ToString(); + server.Name = message["d"]["name"].ToString(); + server.Members = new Dictionary(); + server.Channels = new List(); + server.Roles = new List(); + foreach (var roll in message["d"]["roles"]) + { + var t = new DiscordRole + { + Color = new Color(roll["color"].ToObject().ToString("x")), + Name = roll["name"].ToString(), + Permissions = new DiscordPermission(roll["permissions"].ToObject()), + Position = roll["position"].ToObject(), + Managed = roll["managed"].ToObject(), + ID = roll["id"].ToString(), + Hoist = roll["hoist"].ToObject() + }; + server.Roles.Add(t); + } + foreach (var chn in message["d"]["channels"]) + { + var tempChannel = new DiscordChannel(); + tempChannel.Client = this; + tempChannel.ID = chn["id"].ToString(); + tempChannel.Type = chn["type"].ToObject(); + + if (!chn["topic"].IsNullOrEmpty()) + tempChannel.Topic = chn["topic"].ToString(); + if (tempChannel.Type == ChannelType.Voice && !chn["bitrate"].IsNullOrEmpty()) + tempChannel.Bitrate = chn["bitrate"].ToObject(); + + tempChannel.Name = chn["name"].ToString(); + tempChannel.Private = false; + tempChannel.PermissionOverrides = new List(); + tempChannel.Parent = server; + foreach (var o in chn["permission_overwrites"]) + { + if (tempChannel.Type == ChannelType.Voice) + continue; + var dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); + dpo.id = o["id"].ToString(); + + if (o["type"].ToString() == "member") + dpo.type = DiscordPermissionOverride.OverrideType.member; + else + dpo.type = DiscordPermissionOverride.OverrideType.role; + + tempChannel.PermissionOverrides.Add(dpo); + } + server.Channels.Add(tempChannel); + } + foreach (var mbr in message["d"]["members"]) + { + var member = JsonConvert.DeserializeObject(mbr["user"].ToString()); + if (mbr["nick"] != null) + member.Nickname = mbr["nick"].ToString(); + + member.parentclient = this; + member.Parent = server; + + foreach (var rollid in mbr["roles"]) + member.Roles.Add(server.Roles.Find(x => x.ID == rollid.ToString())); + if (member.Roles.Count == 0) + member.Roles.Add(server.Roles.Find(x => x.Name == "@everyone")); + server.AddMember(member); + } + foreach (var voiceStateJSON in message["d"]["voice_states"]) + { + var voiceState = JsonConvert.DeserializeObject(voiceStateJSON.ToString()); + var member = server.GetMemberByKey(voiceState.UserID); + + member.CurrentVoiceChannel = server.Channels.Find(x => x.ID == voiceState.ChannelID); + member.VoiceState = voiceState; + } + server.Owner = server.GetMemberByKey(message["d"]["owner_id"].ToString()); + e.Server = server; + + if (!message["d"]["unavailable"].IsNullOrEmpty() && message["d"]["unavailable"].ToObject() == false) + { + var oldServer = ServersList.Find(x => x.ID == server.ID); + if (oldServer != null && oldServer.Unavailable) + ServersList.Remove(oldServer); + + ServersList.Add(server); + + GetTextClientLogger.Log($"Guild with ID {server.ID} ({server.Name}) became available."); + GuildAvailable?.Invoke(this, e); + return; + } + + ServersList.Add(server); + GuildCreated?.Invoke(this, e); + } + + private void GuildUpdateEvents(JObject message) + { + var oldServer = ServersList.Find(x => x.ID == message["d"]["id"].ToString()); + var newServer = oldServer.ShallowCopy(); + + newServer.Name = message["d"]["name"].ToString(); + newServer.ID = message["d"]["id"].ToString(); + newServer.parentclient = this; + newServer.Roles = new List(); + newServer.Region = message["d"]["region"].ToString(); + if (!message["d"]["icon"].IsNullOrEmpty()) + newServer.icon = message["d"]["icon"].ToString(); + if (!message["d"]["roles"].IsNullOrEmpty()) + foreach (var roll in message["d"]["roles"]) + { + var t = new DiscordRole + { + Color = new Color(roll["color"].ToObject().ToString("x")), + Name = roll["name"].ToString(), + Permissions = new DiscordPermission(roll["permissions"].ToObject()), + Position = roll["position"].ToObject(), + Managed = roll["managed"].ToObject(), + ID = roll["id"].ToString(), + Hoist = roll["hoist"].ToObject() + }; + newServer.Roles.Add(t); + } + else + newServer.Roles = oldServer.Roles; + newServer.Channels = new List(); + if (!message["d"]["channels"].IsNullOrEmpty()) + foreach (var u in message["d"]["channels"]) + { + var tempSub = new DiscordChannel(); + tempSub.Client = this; + tempSub.ID = u["id"].ToString(); + tempSub.Name = u["name"].ToString(); + tempSub.Type = u["type"].ToObject(); + + if (!u["topic"].IsNullOrEmpty()) + tempSub.Topic = u["topic"].ToString(); + if (tempSub.Type == ChannelType.Voice && !u["bitrate"].IsNullOrEmpty()) + tempSub.Bitrate = u["bitrate"].ToObject(); + + tempSub.Parent = newServer; + var permissionoverrides = new List(); + foreach (var o in u["permission_overwrites"]) + { + var dpo = + new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); + dpo.type = o["type"].ToObject(); + dpo.id = o["id"].ToString(); + + permissionoverrides.Add(dpo); + } + tempSub.PermissionOverrides = permissionoverrides; + + newServer.Channels.Add(tempSub); + } + else + newServer.Channels = oldServer.Channels; + if (!message["d"]["members"].IsNullOrEmpty()) + foreach (var mm in message["d"]["members"]) + { + var member = JsonConvert.DeserializeObject(mm["user"].ToString()); + member.parentclient = this; + member.Parent = newServer; + + var rawRoles = JArray.Parse(mm["roles"].ToString()); + if (rawRoles.Count > 0) + foreach (var role in rawRoles.Children()) + member.Roles.Add(newServer.Roles.Find(x => x.ID == role.Value())); + else + member.Roles.Add(newServer.Roles.Find(x => x.Name == "@everyone")); + + newServer.AddMember(member); + } + else + newServer.Members = oldServer.Members; + if (!message["d"]["owner_id"].IsNullOrEmpty()) + { + newServer.Owner = newServer.GetMemberByKey(message["d"]["owner_id"].ToString()); + GetTextClientLogger.Log( + $"Transferred ownership from user '{oldServer.Owner.Username}' to {newServer.Owner.Username}."); + } + ServersList.Remove(oldServer); + ServersList.Add(newServer); + var dsuea = new DiscordServerUpdateEventArgs { NewServer = newServer, OldServer = oldServer }; + GuildUpdated?.Invoke(this, dsuea); + } + + private void GuildDeleteEvents(JObject message) + { + var e = new DiscordGuildDeleteEventArgs(); + e.Server = ServersList.Find(x => x.ID == message["d"]["id"].ToString()); + e.RawJson = message; + ServersList.Remove(e.Server); + if (GuildDeleted != null) + GuildDeleted(this, e); + } + + private void GuildRoleUpdateEvents(JObject message) + { + var inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + var roleUpdated = new DiscordRole + { + Name = message["d"]["role"]["name"].ToString(), + Position = message["d"]["role"]["position"].ToObject(), + Permissions = new DiscordPermission(message["d"]["role"]["permissions"].ToObject()), + Managed = message["d"]["role"]["managed"].ToObject(), + Hoist = message["d"]["role"]["hoist"].ToObject(), + Color = new Color(message["d"]["role"]["color"].ToObject().ToString("x")), + ID = message["d"]["role"]["id"].ToString() + }; + + ServersList.Find(x => x.ID == inServer.ID) + .Roles.Remove(ServersList.Find(x => x.ID == inServer.ID).Roles.Find(y => y.ID == roleUpdated.ID)); + ServersList.Find(x => x.ID == inServer.ID).Roles.Add(roleUpdated); + + RoleUpdated?.Invoke(this, + new DiscordGuildRoleUpdateEventArgs + { + RawJson = message, + RoleUpdated = roleUpdated, + InServer = inServer + }); + } + + private void GuildRoleDeleteEvents(JObject message) + { + var inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + var deletedRole = inServer.Roles.Find(x => x.ID == message["d"]["role_id"].ToString()); + + try + { + ServersList.Find(x => x.ID == inServer.ID) + .Roles.Remove(ServersList.Find(x => x.ID == inServer.ID).Roles.Find(y => y.ID == deletedRole.ID)); + } + catch (Exception ex) + { + GetTextClientLogger.Log($"Couldn't delete role with ID {message["d"]["role_id"]}! ({ex.Message})", + MessageLevel.Critical); + } + + RoleDeleted?.Invoke(this, + new DiscordGuildRoleDeleteEventArgs { DeletedRole = deletedRole, Guild = inServer, RawJson = message }); + } + + private void UserUpdateEvents(JObject message) + { + var e = new DiscordUserUpdateEventArgs(); + e.RawJson = message; + var newMember = JsonConvert.DeserializeObject(message["d"].ToString()); + newMember.parentclient = this; + + var oldMember = new DiscordMember(this); + oldMember.parentclient = this; + //Update members + foreach (var server in ServersList) + foreach (var member in server.Members) + if (member.Value.ID == newMember.ID) + { + oldMember = member.Value; + server.AddMember(newMember); + break; + } + + newMember.Parent = oldMember.Parent; + + if (!message["roles"].IsNullOrEmpty()) + { + var rawRoles = JArray.Parse(message["roles"].ToString()); + if (rawRoles.Count > 0) + foreach (var role in rawRoles.Children()) + newMember.Roles.Add(newMember.Parent.Roles.Find(x => x.ID == role.ToString())); + else + newMember.Roles.Add(newMember.Parent.Roles.Find(x => x.Name == "@everyone")); + } + + e.NewMember = newMember; + e.OriginalMember = oldMember; + UserUpdate?.Invoke(this, e); + } + + private void PresenceUpdateEvents(JObject message) + { + var dpuea = new DiscordPresenceUpdateEventArgs(); + dpuea.RawJson = message; + + if (!message["d"]["guild_id"].IsNullOrEmpty()) + { + var server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + if (server != null) + { + var user = server.GetMemberByKey(message["d"]["user"]["id"].ToString().Trim()); + if (user != null) + { + //If usernames change. + if (!message["d"]["user"]["username"].IsNullOrEmpty()) + user.Username = message["d"]["user"]["username"].ToString(); + + //If avatar changes. + if (!message["d"]["user"]["avatar"].IsNullOrEmpty()) + user.Avatar = message["d"]["user"]["avatar"].ToString(); + + if (message["d"]["nick"].ToString() == null) + user.Nickname = null; + else + user.Nickname = message["d"]["nick"].ToString(); + + //Actual presence update + user.SetPresence(message["d"]["status"].ToString()); + + //Updating games. + var game = message["d"]["game"].ToString(); + if (message["d"]["game"].IsNullOrEmpty()) //null means not playing + { + dpuea.Game = ""; + user.CurrentGame = null; + } + else + { + if (message["d"]["game"]["name"].IsNullOrEmpty()) + if (message["d"]["game"]["game"].IsNullOrEmpty()) + dpuea.Game = ""; + else + dpuea.Game = message["d"]["game"]["game"].ToString(); + else + dpuea.Game = message["d"]["game"]["name"].ToString(); + user.CurrentGame = dpuea.Game; + + if (message["d"]["game"]["type"] != null && + message["d"]["game"]["type"].ToObject() == 1) + { + user.Streaming = true; + if (message["d"]["game"]["url"].ToString() != null) + user.StreamURL = message["d"]["game"]["url"].ToString(); + } + } + dpuea.User = user; + + if (message["d"]["status"].ToString() == "online") + dpuea.Status = DiscordUserStatus.ONLINE; + else if (message["d"]["status"].ToString() == "idle") + dpuea.Status = DiscordUserStatus.IDLE; + else if (message["d"]["status"].ToString() == null || + message["d"]["status"].ToString() == "offline") + dpuea.Status = DiscordUserStatus.OFFLINE; + if (PresenceUpdated != null) + PresenceUpdated(this, dpuea); + } + else + { + if (!message["d"]["guild_id"].IsNullOrEmpty() + ) //if this is null or empty, that means this pertains to friends list + if (!message["d"]["user"]["username"].IsNullOrEmpty() && + !message["d"]["user"]["id"].IsNullOrEmpty()) + { + GetTextClientLogger.Log( + $"User {message["d"]["user"]["username"]} ({message["d"]["user"]["id"]}) doesn't exist in server {server.Name} ({server.ID}) no problemo. Creating/adding", + MessageLevel.Debug); + var memeber = + JsonConvert.DeserializeObject(message["d"]["user"].ToString()); + memeber.parentclient = this; + memeber.SetPresence(message["d"]["status"].ToString()); + memeber.Parent = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + + if (message["d"]["game"].IsNullOrEmpty()) + { + dpuea.Game = ""; + memeber.CurrentGame = null; + } + else + { + dpuea.Game = message["d"]["game"]["name"].ToString(); + memeber.CurrentGame = dpuea.Game; + if (message["d"]["game"]["type"].ToObject() == 1) + { + user.Streaming = true; + if (message["d"]["game"]["url"].ToString() != null) + user.StreamURL = message["d"]["game"]["url"].ToString(); + } + } + + if (message["d"]["status"].ToString() == "online") + dpuea.Status = DiscordUserStatus.ONLINE; + else if (message["d"]["status"].ToString() == "idle") + dpuea.Status = DiscordUserStatus.IDLE; + else if (message["d"]["status"].ToString() == null || + message["d"]["status"].ToString() == "offline") + dpuea.Status = DiscordUserStatus.OFFLINE; + + memeber.Parent.AddMember(memeber); + } + } + } + } + } + + private void ChannelCreateEvents(JObject message) + { + if (message["d"]["is_private"].ToString().ToLower() == "false") + { + var foundServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); + if (foundServer != null) + { + var tempChannel = new DiscordChannel(); + tempChannel.Client = this; + tempChannel.Name = message["d"]["name"].ToString(); + tempChannel.Type = message["d"]["type"].ToObject(); + if (tempChannel.Type == ChannelType.Voice && !message["d"]["bitrate"].IsNullOrEmpty()) + tempChannel.Bitrate = message["d"]["bitrate"].ToObject(); + + tempChannel.ID = message["d"]["id"].ToString(); + tempChannel.Parent = foundServer; + foundServer.Channels.Add(tempChannel); + var fae = new DiscordChannelCreateEventArgs(); + fae.ChannelCreated = tempChannel; + fae.ChannelType = DiscordChannelCreateType.CHANNEL; + if (ChannelCreated != null) + ChannelCreated(this, fae); + } + } + else + { + var tempPrivate = new DiscordPrivateChannel(); + tempPrivate.Client = this; + tempPrivate.ID = message["d"]["id"].ToString(); + var recipient = ServersList + .Find(x => x.GetMemberByKey(message["d"]["recipient"]["id"].ToString()) != null) + .GetMemberByKey(message["d"]["recipient"]["id"].ToString()); + tempPrivate.Recipient = recipient; + PrivateChannels.Add(tempPrivate); + var fak = new DiscordPrivateChannelEventArgs + { + ChannelType = DiscordChannelCreateType.PRIVATE, + ChannelCreated = tempPrivate + }; + if (PrivateChannelCreated != null) + PrivateChannelCreated(this, fak); + } + } + + private void ChannelUpdateEvents(JObject message) + { + var e = new DiscordChannelUpdateEventArgs(); + e.RawJson = message; + var oldChannel = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["id"].ToString()) != null) + .Channels.Find(x => x.ID == message["d"]["id"].ToString()); + e.OldChannel = oldChannel.ShallowCopy(); + var newChannel = oldChannel; + newChannel.Name = message["d"]["name"].ToString(); + if (!message["d"]["topic"].IsNullOrEmpty()) + newChannel.Topic = message["d"]["topic"].ToString(); + else + newChannel.Topic = oldChannel.Topic; + + newChannel.Private = message["d"]["is_private"].ToObject(); + + var permissionoverrides = new List(); + foreach (var o in message["d"]["permission_overwrites"]) + { + var dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); + dpo.type = o["type"].ToObject(); + dpo.id = o["id"].ToString(); + + permissionoverrides.Add(dpo); + } + newChannel.PermissionOverrides = permissionoverrides; + + e.NewChannel = newChannel; + + var serverToRemoveFrom = ServersList.Find(x => x.Channels.Find(y => y.ID == newChannel.ID) != null); + newChannel.Parent = serverToRemoveFrom; + var indexOfServer = ServersList.IndexOf(serverToRemoveFrom); + serverToRemoveFrom.Channels.Remove(oldChannel); + serverToRemoveFrom.Channels.Add(newChannel); + + ServersList.RemoveAt(indexOfServer); + ServersList.Insert(indexOfServer, serverToRemoveFrom); + + if (ChannelUpdated != null) + ChannelUpdated(this, e); + } + + private void ChannelDeleteEvents(JObject message) + { + if (!message["d"]["recipient"].IsNullOrEmpty()) + { + //private channel removed + var e = new DiscordPrivateChannelDeleteEventArgs(); + e.PrivateChannelDeleted = PrivateChannels.Find(x => x.ID == message["d"]["id"].ToString()); + if (e.PrivateChannelDeleted != null) + { + if (PrivateChannelDeleted != null) + PrivateChannelDeleted(this, e); + PrivateChannels.Remove(e.PrivateChannelDeleted); + } + else + { + GetTextClientLogger.Log("Error in ChannelDeleteEvents: PrivateChannel is null!", + MessageLevel.Error); + } + } + else + { + var e = new DiscordChannelDeleteEventArgs + { + ChannelDeleted = GetChannelByID(message["d"]["id"].ToObject()) + }; + DiscordServer server; + server = e.ChannelDeleted.Parent; + server.Channels.Remove(server.Channels.Find(x => x.ID == e.ChannelDeleted.ID)); + + if (ChannelDeleted != null) + ChannelDeleted(this, e); + } + } + + private void MessageCreateEvents(JObject message) + { + var potentialChannel = GetDiscordChannelByID(message["d"]["channel_id"].ToString()); + if (potentialChannel == null) //private message create + { + var dpmea = new DiscordPrivateMessageEventArgs + { + Channel = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()), + MessageDirection = message["d"]["author"]["id"].ToString() == Me.ID + ? DiscordMessageDirection.Out + : DiscordMessageDirection.In, + Message = message["d"]["content"].ToString(), + Attachments = message["d"]["attachments"] + .AsEnumerable() + .Select(m => m.ToObject()) + .ToArray(), + RawJson = message, + BaseMessage = BaseMessage.TryParse(message["d"]), + + Author = new DiscordMember(this) + { + Username = message["d"]["author"]["username"].ToString(), + ID = message["d"]["author"]["id"].ToString(), + parentclient = this + } + }; + + PrivateMessageReceived?.Invoke(this, dpmea); + } + else + { + var dmea = new DiscordMessageEventArgs(); + dmea.RawJson = message; + dmea.Channel = potentialChannel; + + dmea.MessageText = message["d"]["content"].ToString(); + + DiscordMember tempMember = null; + tempMember = potentialChannel.Parent.GetMemberByKey(message["d"]["author"]["id"].ToString()); + if (tempMember == null) + { + tempMember = JsonConvert.DeserializeObject(message["author"].ToString()); + tempMember.parentclient = this; + tempMember.Parent = potentialChannel.Parent; + + potentialChannel.Parent.AddMember(tempMember); + } + + dmea.Author = tempMember; + + var m = new DiscordMessage(); + m.Author = dmea.Author; + m.channel = dmea.Channel; + m.TypeOfChannelObject = dmea.Channel.GetType(); + m.Content = dmea.MessageText; + m.ID = message["d"]["id"].ToString(); + m.RawJson = message; + m.timestamp = DateTime.Now; + dmea.Message = m; + if (!message["d"]["attachments"].IsNullOrEmpty()) + { + var tempList = new List(); + foreach (var attachment in message["d"]["attachments"]) + tempList.Add(JsonConvert.DeserializeObject(attachment.ToString())); + m.Attachments = tempList.ToArray(); + } + + if (!message["d"]["mentions"].IsNullOrEmpty()) + { + var mentionsAsArray = JArray.Parse(message["d"]["mentions"].ToString()); + foreach (var mention in mentionsAsArray) + { + var id = mention["id"].ToString(); + if (id.Equals(Me.ID)) + if (MentionReceived != null) + MentionReceived(this, dmea); + } + } + + var toAdd = new KeyValuePair(message["d"]["id"].ToString(), m); + MessageLog.Add(message["d"]["id"].ToString(), m); + + MessageReceived?.Invoke(this, dmea); + } + //} + //catch (Exception ex) + //{ + // DebugLogger.Log("Error ocurred during MessageCreateEvents: " + ex.Message, MessageLevel.Error); + //} + } + + private void MessageUpdateEvents(JObject message) + { + try + { + var pserver = ServersList.Find( + x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); + if (pserver == null) + { + //Private message? + var channel = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + if (channel == null) return; + + //Private message! + var dpmea = new DiscordPrivateMessageEventArgs + { + Channel = channel, + MessageDirection = message["d"]["author"]["id"].ToString() == Me.ID + ? DiscordMessageDirection.Out + : DiscordMessageDirection.In, + Message = message["d"]["content"].ToString(), + Attachments = message["d"]["attachments"] + .AsEnumerable() + .Select(m => m.ToObject()) + .ToArray(), + RawJson = message, + BaseMessage = BaseMessage.TryParse(message["d"]), + Author = new DiscordMember(this) + { + Username = message["d"]["author"]["username"].ToString(), + ID = message["d"]["author"]["id"].ToString(), + parentclient = this + } + }; + PrivateMessageUpdate?.Invoke(this, dpmea); + } + else + { + var pchannel = pserver.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + if (pchannel != null) + if (message["d"]["author"] != null) + { + var toRemove = FindInMessageLog(message["d"]["id"].ToString()); + if (toRemove == null) + return; //No message exists + var jsonToEdit = toRemove.RawJson; + jsonToEdit["d"]["content"].Replace(JToken.FromObject(message["d"]["content"].ToString())); + if (MessageEdited != null) + MessageEdited(this, new DiscordMessageEditedEventArgs + { + Author = pserver.GetMemberByKey(message["d"]["author"]["id"].ToString()), + Channel = pchannel, + MessageText = message["d"]["content"].ToString(), + MessageType = DiscordMessageType.CHANNEL, + MessageEdited = new DiscordMessage + { + Author = pserver.GetMemberByKey(message["d"]["author"]["id"].ToString()), + Content = toRemove.Content, + Attachments = message["d"]["attachments"].ToObject(), + channel = + pserver.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()), + RawJson = message, + ID = message["d"]["id"].ToString(), + timestamp = message["d"]["timestamp"].ToObject() + }, + EditedTimestamp = message["d"]["edited_timestamp"].ToObject() + }); + MessageLog.Remove(message["d"]["id"].ToString()); + + var newMessage = toRemove; + newMessage.Content = jsonToEdit["d"]["content"].ToString(); + MessageLog.Add(message["d"]["id"].ToString(), newMessage); + } + else //I know they say assume makes an ass out of you and me...but we're assuming it's Discord's weird auto edit of a just URL message + { + if (URLMessageAutoUpdate != null) + { + var asdf = + new + DiscordURLUpdateEventArgs(); //I'm running out of clever names and should probably split these off into different internal voids soon... + asdf.ID = message["d"]["id"].ToString(); + asdf.Channel = ServersList + .Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != + null) + .Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + foreach (var embed in message["d"]["embeds"]) + { + var temp = new DiscordEmbeds(); + temp.URL = embed["url"].ToString(); + temp.Description = embed["description"].ToString(); + try + { + temp.ProviderName = + embed["provider"]["name"] == null + ? null + : embed["provider"]["name"].ToString(); + temp.ProviderURL = embed["provider"]["url"].ToString(); + } + catch + { + } //noprovider + temp.Title = embed["title"].ToString(); + temp.Type = embed["type"].ToString(); + asdf.Embeds.Add(temp); + } + URLMessageAutoUpdate(this, asdf); + } + } + else + GetTextClientLogger.Log("Couldn't find channel!", MessageLevel.Critical); + } + } + catch (Exception ex) + { + GetTextClientLogger.Log( + $"Exception during MessageUpdateEvents.\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", + MessageLevel.Critical); + } + } + + private void MessageDeletedEvents(JObject message) + { + var e = new DiscordMessageDeletedEventArgs(); + e.DeletedMessage = FindInMessageLog(message["d"]["id"].ToString()); + + DiscordServer inServer; + inServer = ServersList.Find( + x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); + if (inServer == null) //dm delete + { + var dm = new DiscordPrivateMessageDeletedEventArgs(); + dm.DeletedMessage = e.DeletedMessage; + dm.RawJson = message; + dm.Channel = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + if (PrivateMessageDeleted != null) + PrivateMessageDeleted(this, dm); + } + else + { + e.Channel = inServer.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); + e.RawJson = message; + } + + if (MessageDeleted != null) + MessageDeleted(this, e); + } + } +} \ No newline at end of file diff --git a/DiscordSharp/DiscordClient.cs b/DiscordSharp/DiscordClient.cs deleted file mode 100644 index 9635e42..0000000 --- a/DiscordSharp/DiscordClient.cs +++ /dev/null @@ -1,3396 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using System.Net; -using System.IO; -using WebSocketSharp; -using System.Threading; -using System.Collections.Generic; -using Newtonsoft.Json.Linq; -using DiscordSharp.Events; -using System.Text.RegularExpressions; -using System.Drawing; -using DiscordSharp.Objects; - -using ID = System.String; -using DiscordSharp.Sockets; -using System.Linq; - -namespace DiscordSharp -{ - /// - /// Properties that Discord uses upon connection to the websocket. Mostly used for analytics internally. - /// - public class DiscordProperties - { - /// - /// The OS you're on - /// - [JsonProperty("$os")] - public string OS { get; set; } - - /// - /// The "browser" you're using. - /// - [JsonProperty("$browser")] - public string Browser { get; set; } - - /// - /// Whatever device you want to be on. (Default: DiscordSharp Bot) - /// - [JsonProperty("$device")] - public string Device - { get; set; } = "DiscordSharp Bot"; - - /// - /// - /// - [JsonProperty("$referrer")] - public string referrer { get; set; } - - /// - /// - /// - [JsonProperty("$referring_domain")] - public string referring_domain { get; set; } - - /// - /// Default constructor setting the OS property to Environment.OSVersion.ToString(); - /// - public DiscordProperties() - { - OS = Environment.OSVersion.ToString(); - } - - /// - /// Serializes this object as json. - /// - /// Json string of this object serialized - public string AsJson() - { - return JsonConvert.SerializeObject(this); - } - } - - /// - /// The type of message that the Discord message is. - /// - public enum DiscordMessageType - { - /// - /// Private/DM - /// - PRIVATE, - - /// - /// Public/in a channel. - /// - CHANNEL - } - - /// - /// Where the magic happens. - /// - public class DiscordClient - { - /// - /// The token associated with authenticating your bot and ensuring they can send messages. - /// - public static string token { get; internal set; } = null; - - /// - /// If this is true, the account this client is running on is a special bot account. - /// - public static bool IsBotAccount { get; internal set; } = false; - - /// - /// The URL that the client websocket is currently connected to. - /// - public string CurrentGatewayURL { get; internal set; } - - /// - /// Any private information assosciated with the account (regular clients only) - /// - public DiscordUserInformation ClientPrivateInformation { get; set; } - - /// - /// Custom properties containing parameters such as - /// * OS - /// * Referrer - /// * Browser - /// Used by Discord internally for connection. - /// - public DiscordProperties DiscordProperties { get; set; } = new DiscordProperties(); - - /// - /// The current DiscordMember object assosciated with the account you're connected to. - /// - public DiscordMember Me { get; internal set; } - - /// - /// Returns the debug logger used to log various debug events. - /// - public Logger GetTextClientLogger => DebugLogger; - - /// - /// Returns the last debug logger for when the voice client was last connected. - /// - public Logger GetLastVoiceClientLogger; - - /// - /// If true, the logger will log everything. - /// Everything. - /// - public bool EnableVerboseLogging { get; set; } = false; - - /// - /// The version of the gateway. - /// - public int DiscordGatewayVersion { get; set; } = 0; - - [Obsolete] - internal bool V4Testing { get; set; } = false; - - /// - /// V4 related things. Getting this means our session has been successfully initiated. - /// - private string SessionID; - - /// - /// The last sequence we received used for v4 heartbeat. - /// - private int Sequence = 0; - - /// - /// Whether or not to send Opcode 6 (resume) upon a socket being closed. - /// - public bool Autoconnect { get; set; } = true; - - private IDiscordWebSocket ws; - private List ServersList { get; set; } - private string CurrentGameName = ""; - private int? IdleSinceUnixTime = null; - private static string UserAgentString = $" (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version.ToString()})"; - private DiscordVoiceClient VoiceClient; - private Logger DebugLogger = new Logger(); - private CancellationTokenSource KeepAliveTaskTokenSource = new CancellationTokenSource(); - private CancellationToken KeepAliveTaskToken; - private Task KeepAliveTask; - private Thread VoiceThread; //yuck - private static string StrippedEmail = ""; - - /// - /// Testing. - /// - private List RemovedMembers = new List(); - - /// - /// Whether or not to write the latest READY upon receiving it. - /// If this is true, the client will write the contents of the READY message to 'READY_LATEST.txt' - /// If your client is connected to a lot of servers, this file will be quite large. - /// - public bool WriteLatestReady { get; set; } = false; - - /// - /// Whether or not to request all users in a guild (including offlines) on startup. - /// - public bool RequestAllUsersOnStartup { get; set; } = false; - - /// - /// A log of messages kept in a KeyValuePair. - /// The key is the id of the message, and the value is a DiscordMessage object. If you need raw json, this is contained inside of the DiscordMessage object now. - /// - private Dictionary MessageLog = new Dictionary(); - - //private List> MessageLog = new List>(); - private List PrivateChannels = new List(); - - #region Event declaration - - public event EventHandler MessageReceived; - - public event EventHandler Connected; - - public event EventHandler SocketOpened; - - public event EventHandler SocketClosed; - - public event EventHandler ChannelCreated; - - public event EventHandler PrivateChannelCreated; - - public event EventHandler PrivateMessageReceived; - - public event EventHandler KeepAliveSent; - - public event EventHandler MentionReceived; - - public event EventHandler UserTypingStart; - - public event EventHandler MessageEdited; - - public event EventHandler PresenceUpdated; - - public event EventHandler URLMessageAutoUpdate; - - public event EventHandler VoiceStateUpdate; - - public event EventHandler UnknownMessageTypeReceived; - - public event EventHandler MessageDeleted; - - public event EventHandler UserUpdate; - - public event EventHandler UserAddedToServer; - - public event EventHandler UserRemovedFromServer; - - public event EventHandler GuildCreated; - - /// - /// Occurs when a guild becomes available after being unavailable. - /// - public event EventHandler GuildAvailable; - - public event EventHandler GuildDeleted; - - public event EventHandler ChannelUpdated; - - public event EventHandler TextClientDebugMessageReceived; - - public event EventHandler VoiceClientDebugMessageReceived; - - public event EventHandler ChannelDeleted; - - public event EventHandler GuildUpdated; - - public event EventHandler RoleDeleted; - - public event EventHandler RoleUpdated; - - public event EventHandler GuildMemberUpdated; - - public event EventHandler GuildMemberBanned; - - public event EventHandler PrivateChannelDeleted; - - public event EventHandler BanRemoved; - - public event EventHandler PrivateMessageDeleted; - - #region Voice - - /// - /// For use when connected to voice only. - /// - public event EventHandler AudioPacketReceived; - - /// - /// For use when connected to voice only. - /// - public event EventHandler UserSpeaking; - - /// - /// For use when connected to voice only. - /// - public event EventHandler UserLeftVoiceChannel; - - /// - /// Occurs when the voice client is fully connected to voice. - /// - public event EventHandler VoiceClientConnected; - - /// - /// Occurs when the voice queue is emptied. - /// - public event EventHandler VoiceQueueEmpty; - - #endregion Voice - - #endregion Event declaration - - /// - /// - /// - /// If you have a token you wish to use, provide it here. Else, a login attempt will be made. - /// Set this to true if your bot is going to be a bot account - public DiscordClient(string tokenOverride = null, bool isBotAccount = false, bool enableLogging = true) - { - if (isBotAccount && tokenOverride == null) - throw new Exception("Token override cannot be null if using a bot account!"); - DebugLogger.EnableLogging = enableLogging; - - token = tokenOverride; - IsBotAccount = isBotAccount; - - if (IsBotAccount) - UserAgentString = "DiscordBot " + UserAgentString; - else - UserAgentString = "Custom Discord Client " + UserAgentString; - - if (ClientPrivateInformation == null) - ClientPrivateInformation = new DiscordUserInformation(); - - DebugLogger.LogMessageReceived += (sender, e) => - { - if (e.message.Level == MessageLevel.Error) - DisconnectFromVoice(); - if (TextClientDebugMessageReceived != null) - TextClientDebugMessageReceived(this, e); - }; - } - - /// - /// Current DiscordServers you're connected to. - /// - /// DiscordServer list of servers you're currently connected to. - public List GetServersList() => ServersList; - - /// - /// Any messages logged since connection to the websocket. - /// - /// A KeyValuePair list of string-DiscordMessage. Where string is the message's ID - public Dictionary GetMessageLog() => MessageLog; - - /// - /// Private channels assosciated with the account. - /// - /// a list of DiscordPrivateChannels. - public List GetPrivateChannels() => PrivateChannels; - - /// - /// - /// - /// True if connected to voice. - public bool ConnectedToVoice() => VoiceClient != null ? VoiceClient.Connected : false; - - //eh - private void GetChannelsList(JObject m) - { - if (ServersList == null) - ServersList = new List(); - foreach (var j in m["d"]["guilds"]) - { - if (!j["unavailable"].IsNullOrEmpty() && j["unavailable"].ToObject() == true) - continue; //unavailable server - DiscordServer temp = new DiscordServer(); - temp.parentclient = this; - temp.JoinedAt = j["joined_at"].ToObject(); - temp.ID = j["id"].ToString(); - temp.Name = j["name"].ToString(); - if (!j["icon"].IsNullOrEmpty()) - temp.icon = j["icon"].ToString(); - else - temp.icon = null; - - //temp.owner_id = j["owner_id"].ToString(); - List tempSubs = new List(); - - List tempRoles = new List(); - foreach (var u in j["roles"]) - { - DiscordRole t = new DiscordRole - { - Color = new DiscordSharp.Color(u["color"].ToObject().ToString("x")), - Name = u["name"].ToString(), - Permissions = new DiscordPermission(u["permissions"].ToObject()), - Position = u["position"].ToObject(), - Managed = u["managed"].ToObject(), - ID = u["id"].ToString(), - Hoist = u["hoist"].ToObject() - }; - tempRoles.Add(t); - } - temp.Roles = tempRoles; - foreach (var u in j["channels"]) - { - DiscordChannel tempSub = new DiscordChannel(); - tempSub.Client = this; - tempSub.ID = u["id"].ToString(); - tempSub.Name = u["name"].ToString(); - tempSub.Type = u["type"].ToObject(); - if (!u["topic"].IsNullOrEmpty()) - tempSub.Topic = u["topic"].ToString(); - if (tempSub.Type == ChannelType.Voice && !u["bitrate"].IsNullOrEmpty()) - tempSub.Bitrate = u["bitrate"].ToObject(); - tempSub.Parent = temp; - List permissionoverrides = new List(); - foreach (var o in u["permission_overwrites"]) - { - DiscordPermissionOverride dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); - dpo.id = o["id"].ToString(); - - if (o["type"].ToString() == "member") - dpo.type = DiscordPermissionOverride.OverrideType.member; - else - dpo.type = DiscordPermissionOverride.OverrideType.role; - - permissionoverrides.Add(dpo); - } - tempSub.PermissionOverrides = permissionoverrides; - - tempSubs.Add(tempSub); - } - temp.Channels = tempSubs; - foreach (var mm in j["members"]) - { - DiscordMember member = JsonConvert.DeserializeObject(mm["user"].ToString()); - member.parentclient = this; - member.Roles = new List(); - JArray rawRoles = JArray.Parse(mm["roles"].ToString()); - if (rawRoles.Count > 0) - { - foreach (var role in rawRoles.Children()) - { - member.Roles.Add(temp.Roles.Find(x => x.ID == role.Value())); - } - } - else - { - member.Roles.Add(temp.Roles.Find(x => x.Name == "@everyone")); - } - temp.AddMember(member); - } - if (!j["presences"].IsNullOrEmpty()) - { - foreach (var presence in j["presences"]) - { - DiscordMember member = temp.GetMemberByKey(presence["user"]["id"].ToString()); - if (member != null) - { - member.SetPresence(presence["status"].ToString()); - if (!presence["game"].IsNullOrEmpty()) - { - member.CurrentGame = presence["game"]["name"].ToString(); - if (presence["game"]["type"].ToObject() == 1) - { - member.Streaming = true; - if (presence["game"]["url"].ToString() != null) - member.StreamURL = presence["game"]["url"].ToString(); - } - } - } - } - } - temp.Region = j["region"].ToString(); - temp.Owner = temp.GetMemberByKey(j["owner_id"].ToString()); - ServersList.Add(temp); - } - if (PrivateChannels == null) - PrivateChannels = new List(); - foreach (var privateChannel in m["d"]["private_channels"]) - { - DiscordPrivateChannel tempPrivate = JsonConvert.DeserializeObject(privateChannel.ToString()); - tempPrivate.Client = this; - tempPrivate.user_id = privateChannel["recipient"]["id"].ToString(); - DiscordServer potentialServer = new DiscordServer(); - ServersList.ForEach(x => - { - if (x.GetMemberByKey(privateChannel["recipient"]["id"].ToString()) != null) - { - potentialServer = x; - } - }); - if (potentialServer.Owner != null) //should be a safe test..i hope - { - DiscordMember recipient = potentialServer.GetMemberByKey(privateChannel["recipient"]["id"].ToString()); - if (recipient != null) - { - tempPrivate.Recipient = recipient; - } - else - { - DebugLogger.Log("Recipient was null!!!!", MessageLevel.Critical); - } - } - else - { - DebugLogger.Log("No potential server found for user's private channel null! This will probably fix itself.", MessageLevel.Debug); - } - PrivateChannels.Add(tempPrivate); - } - } - - /// - /// Sends an http DELETE request to leave the server you send in this parameter. - /// - /// The DiscordServer object you want to leave. - public void LeaveServer(DiscordServer server) => LeaveServer(server.ID); - - /// - /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify. - /// - /// The DiscordServer object you want to delete. - public void DeleteServer(DiscordServer server) => DeleteServer(server.ID); - - /// - /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify. - /// - /// The server's ID you want to delete. - public void LeaveServer(string ServerID) - { - string url = //Endpoints.BaseAPI + Endpoints.Guilds + $"/{ServerID}"; - Endpoints.BaseAPI + Endpoints.Users + Endpoints.Me + Endpoints.Guilds + $"/{ServerID}"; //old, left for lulz - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while leaving server ({ServerID}): {ex.Message}", MessageLevel.Error); - } - } - - /// - /// (Owner only, non-bot only) Sends an http DELETE request to delete the server you specify by ID. - /// - /// The server's ID you want to delete. - public void DeleteServer(string ServerID) - { - if (IsBotAccount) - throw new Exception("Bot accounts can't own servers!"); - - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{ServerID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while deleting server ({ServerID}): {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Sends a message to a channel, what else did you expect? - /// - /// The text to send - /// DiscordChannel object to send the message to. - /// A DiscordMessage object of the message sent to Discord. - public DiscordMessage SendMessageToChannel(string message, DiscordChannel channel) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; - try - { - JObject result = JObject.Parse(WebWrapper.Post(url, token, JsonConvert.SerializeObject(Utils.GenerateMessage(message)))); - if (result["content"].IsNullOrEmpty()) - throw new InvalidOperationException("Request returned a blank message, you may not have permission to send messages yet!"); - - DiscordMessage m = new DiscordMessage - { - ID = result["id"].ToString(), - Attachments = result["attachments"].ToObject(), - Author = channel.Parent.GetMemberByKey(result["author"]["id"].ToString()), - channel = channel, - TypeOfChannelObject = channel.GetType(), - Content = result["content"].ToString(), - RawJson = result, - timestamp = result["timestamp"].ToObject() - }; - return m; - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while sending message to channel ({channel.Name}): {ex.Message}", MessageLevel.Error); - } - return null; - } - - /// - /// Sends a file to the specified DiscordChannel with the given message. - /// - /// The channel to send the message to. - /// The message you want the file to have with it. - /// The path to the file you wish to send (be careful!) - public void AttachFile(DiscordChannel channel, string message, string pathToFile) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; - //WebWrapper.PostWithAttachment(url, message, pathToFile); - try - { - var uploadResult = JObject.Parse(WebWrapper.HttpUploadFile(url, token, pathToFile, "file", "image/jpeg", null)); - - if (!string.IsNullOrEmpty(message)) - EditMessage(uploadResult["id"].ToString(), message, channel); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while sending file ({pathToFile}) to {channel.Name}: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Sends a file to the specified DiscordChannel with the given message. - /// - /// The channel to send the message to. - /// The message you want the file to have with it. - /// A stream object to send the bytes from. - public void AttachFile(DiscordChannel channel, string message, System.IO.Stream stream) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages; - //WebWrapper.PostWithAttachment(url, message, pathToFile); - try - { - var uploadResult = JObject.Parse(WebWrapper.HttpUploadFile(url, token, stream, "file", "image/jpeg", null)); - - if (!string.IsNullOrEmpty(message)) - EditMessage(uploadResult["id"].ToString(), message, channel); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while sending file by stream to {channel.Name}: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Changes the current client's avatar. - /// Any high resolution pictures are automatically downscaled and Discord will perform jpeg compression on them. - /// - /// The Bitmap object assosciated with the avatar you wish to upload. - public void ChangeClientAvatar(Bitmap image) - { - string base64 = Convert.ToBase64String(Utils.ImageToByteArray(image)); - string type = "image/jpeg;base64"; - string req = $"data:{type},{base64}"; - string usernameRequestJson = JsonConvert.SerializeObject(new - { - avatar = req, - email = ClientPrivateInformation.Email, - password = ClientPrivateInformation.Password, - username = ClientPrivateInformation.Username - }); - string url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; - try - { - WebWrapper.Patch(url, token, usernameRequestJson); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while changing client's avatar: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Changes the icon assosciated with the guild. Discord will perform jpeg compression and this image is automatically downscaled. - /// - /// The bitmap object associated - /// The guild of the icon you wish to change. - public void ChangeGuildIcon(Bitmap image, DiscordServer guild) - { - Bitmap resized = new Bitmap((Image)image, 200, 200); - - string base64 = Convert.ToBase64String(Utils.ImageToByteArray(resized)); - string type = "image/jpeg;base64"; - string req = $"data:{type},{base64}"; - string guildjson = JsonConvert.SerializeObject(new { icon = req, name = guild.Name }); - string url = Endpoints.BaseAPI + Endpoints.Guilds + "/" + guild.ID; - try - { - var result = JObject.Parse(WebWrapper.Patch(url, token, guildjson)); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while changing guild {guild.Name}'s icon: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Returns a List of DiscordMessages. - /// - /// The channel to return them from. - /// How many to return - /// Messages before this message ID. - /// Messages after this message ID. - /// A List of DiscordMessages that you can iterate through. - public List GetMessageHistory(DiscordChannelBase channel, int count, string idBefore = "", string idAfter = "") - { - string request = "https://discordapp.com/api/channels/" + channel.ID + $"/messages?&limit={count}"; - if (!string.IsNullOrEmpty(idBefore)) - request += $"&before={idBefore}"; - if (string.IsNullOrEmpty(idAfter)) - request += $"&after={idAfter}"; - - JArray result = null; - - try - { - string res = WebWrapper.Get(request, token); - result = JArray.Parse(res); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while getting message history for channel {channel.ID}: {ex.Message}", MessageLevel.Error); - } - - if (result != null) - { - List messageList = new List(); - /// NOTE - /// For some reason, the d object is excluded from this. - foreach (var item in result.Children()) - { - messageList.Add(new DiscordMessage - { - ID = item["id"].ToString(), - channel = channel, - Attachments = item["attachments"].ToObject(), - TypeOfChannelObject = channel.GetType(), - Author = GetMemberFromChannel(channel, item["author"]["id"].ToString()), - Content = item["content"].ToString(), - RawJson = item.ToObject(), - timestamp = DateTime.Parse(item["timestamp"].ToString()) - }); - } - return messageList; - } - - return null; - } - - /// - /// Changes the channel topic assosciated with the Discord text channel. - /// - /// The new channel topic. - /// The channel you wish to change the topic for. - public void ChangeChannelTopic(string Channeltopic, DiscordChannel channel) - { - string topicChangeJson = JsonConvert.SerializeObject( - new - { - name = channel.Name, - topic = Channeltopic - }); - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}"; - try - { - var result = JObject.Parse(WebWrapper.Patch(url, token, topicChangeJson)); - ServersList.Find(x => x.Channels.Find(y => y.ID == channel.ID) != null).Channels.Find(x => x.ID == channel.ID).Topic = Channeltopic; - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while changing channel topic for channel {channel.Name}: {ex.Message}", MessageLevel.Error); - } - } - - /* - public List GetRoles(DiscordServer server) - { - return null; - } - */ - - /// - /// Used for changing the client's email, password, username, etc. - /// - /// - public void ChangeClientInformation(DiscordUserInformation info) - { - string usernameRequestJson; - if (info.Password != ClientPrivateInformation.Password) - { - usernameRequestJson = JsonConvert.SerializeObject(new - { - email = info.Email, - new_password = info.Password, - password = ClientPrivateInformation.Password, - username = info.Username, - avatar = info.Avatar - }); - ClientPrivateInformation.Password = info.Password; - try - { - File.Delete("token_cache"); - DebugLogger.Log("Deleted token_cache due to change of password."); - } - catch (Exception) { /*ignore*/ } - } - else - { - usernameRequestJson = JsonConvert.SerializeObject(new - { - email = info.Email, - password = info.Password, - username = info.Username, - avatar = info.Avatar - }); - } - - string url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; - try - { - var result = JObject.Parse(WebWrapper.Patch(url, token, usernameRequestJson)); - foreach (var server in ServersList) - { - if (server.Members[Me.ID] != null) - server.Members[Me.ID].Username = info.Username; - } - Me.Username = info.Username; - Me.Email = info.Email; - Me.Avatar = info.Avatar; - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while changing client's information: {ex.Message}", MessageLevel.Error); - } - } - - private void ChangeClientUsername(string newUsername) - { - string url = Endpoints.BaseAPI + Endpoints.Users + "/@me"; - string usernameRequestJson = JsonConvert.SerializeObject(new - { - email = ClientPrivateInformation.Email, - password = ClientPrivateInformation.Password, - username = newUsername, - avatar = Me.Avatar, - }); - try - { - var result = JObject.Parse(WebWrapper.Patch(url, token, usernameRequestJson)); - if (result != null) - { - foreach (var server in ServersList) - { - if (server.Members[Me.ID] != null) - server.Members[Me.ID].Username = newUsername; - } - Me.Username = newUsername; - } - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while changing client's username: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Sends a private message to the given user. - /// - /// The message text to send them. - /// The member you want to send this to. - /// - public DiscordMessage SendMessageToUser(string message, DiscordMember member) - { - string url = Endpoints.BaseAPI + Endpoints.Users + $"/{Me.ID}" + Endpoints.Channels; - string initMessage = "{\"recipient_id\":" + member.ID + "}"; - - try - { - var result = JObject.Parse(WebWrapper.Post(url, token, initMessage)); - if (result != null) - { - DiscordMember recipient = ServersList.Find( - x => x.GetMemberByKey(result["recipient"]["id"].ToString()) != null).Members[result["recipient"]["id"].ToString()]; - return SendActualMessage(result["id"].ToString(), message, recipient); - } - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while sending message to user, step 1: {ex.Message}", MessageLevel.Error); - } - - return null; - } - - private DiscordMessage SendActualMessage(string id, string message, DiscordMember recipient) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{id}" + Endpoints.Messages; - DiscordMessage toSend = Utils.GenerateMessage(message); - - try - { - var result = JObject.Parse(WebWrapper.Post(url, token, JsonConvert.SerializeObject(toSend).ToString())); - DiscordMessage d = JsonConvert.DeserializeObject(result.ToString()); - d.Recipient = recipient; - d.channel = PrivateChannels.Find(x => x.ID == result["channel_id"].ToString()); - d.TypeOfChannelObject = typeof(DiscordPrivateChannel); - d.Author = Me; - return d; - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while sending message to user, step 2: {ex.Message}", MessageLevel.Error); - } - return null; - } - - /// - /// Gets the string value of the current game your bot is 'playing'. - /// - public string GetCurrentGame => CurrentGameName; - - /// - /// Returns true if the websocket is not null and is alive. - /// - public bool WebsocketAlive => (ws != null) ? ws.IsAlive : false; - - public bool ReadyComplete { get; private set; } - - #region Message Received Crap.. - - /// - /// Updates the bot's 'Currently playing' status to the following text. Pass in null if you want to remove this. - /// - /// The game's name. Old gameid lookup can be seen at: http://hastebin.com/azijiyaboc.json/ - /// Whether or not you want your bot to appear as if it is streaming. True means it will show it's streaming. - /// The 'url' for the stream, if your bot is streaming. - public void UpdateCurrentGame(string gameName, bool streaming, string url = null) - { - string msg; - if (gameName.ToLower().Trim() != "") - { - msg = JsonConvert.SerializeObject( - new - { - op = 3, - d = new - { - idle_since = IdleSinceUnixTime == null ? (object)null : IdleSinceUnixTime, - game = new - { - name = gameName, - type = streaming ? 1 : 0, - url = (url != null) ? url : (object)null - } - } - }); - CurrentGameName = gameName; - DebugLogger.Log($"Updating client's current game as '{gameName}'"); - } - else - { - msg = JsonConvert.SerializeObject( - new - { - op = 3, - d = new - { - idle_since = IdleSinceUnixTime == null ? (object)null : IdleSinceUnixTime, - game = (object)null - } - }); - DebugLogger.Log("Setting current game to null."); - } - ws.Send(msg.ToString()); - } - - /// - /// Updates the bot's status. - /// - /// True if you want the bot to report as idle. - public void UpdateBotStatus(bool idle) - { - string msg; - msg = JsonConvert.SerializeObject( - new - { - op = 3, - d = new - { - idle_since = idle ? (int)(DateTime.UtcNow - epoch).TotalMilliseconds : (object)null, - game = CurrentGameName.ToLower().Trim() == "" ? (object)null : new { name = CurrentGameName } - } - }); - ws.Send(msg.ToString()); //let's try it! - } - - private void PresenceUpdateEvents(JObject message) - { - DiscordPresenceUpdateEventArgs dpuea = new DiscordPresenceUpdateEventArgs(); - dpuea.RawJson = message; - - if (!message["d"]["guild_id"].IsNullOrEmpty()) - { - var server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - if (server != null) - { - var user = server.GetMemberByKey(message["d"]["user"]["id"].ToString().Trim()); - if (user != null) - { - //If usernames change. - if (!message["d"]["user"]["username"].IsNullOrEmpty()) - user.Username = message["d"]["user"]["username"].ToString(); - - //If avatar changes. - if (!message["d"]["user"]["avatar"].IsNullOrEmpty()) - user.Avatar = message["d"]["user"]["avatar"].ToString(); - - if (message["d"]["nick"].ToString() == null) - user.Nickname = null; - else - user.Nickname = message["d"]["nick"].ToString(); - - //Actual presence update - user.SetPresence(message["d"]["status"].ToString()); - - //Updating games. - string game = message["d"]["game"].ToString(); - if (message["d"]["game"].IsNullOrEmpty()) //null means not playing - { - dpuea.Game = ""; - user.CurrentGame = null; - } - else - { - if (message["d"]["game"]["name"].IsNullOrEmpty()) - if (message["d"]["game"]["game"].IsNullOrEmpty()) - dpuea.Game = ""; - else - dpuea.Game = message["d"]["game"]["game"].ToString(); - else - dpuea.Game = message["d"]["game"]["name"].ToString(); - user.CurrentGame = dpuea.Game; - - if (message["d"]["game"]["type"] != null && message["d"]["game"]["type"].ToObject() == 1) - { - user.Streaming = true; - if (message["d"]["game"]["url"].ToString() != null) - user.StreamURL = message["d"]["game"]["url"].ToString(); - } - } - dpuea.User = user; - - if (message["d"]["status"].ToString() == "online") - dpuea.Status = DiscordUserStatus.ONLINE; - else if (message["d"]["status"].ToString() == "idle") - dpuea.Status = DiscordUserStatus.IDLE; - else if (message["d"]["status"].ToString() == null || message["d"]["status"].ToString() == "offline") - dpuea.Status = DiscordUserStatus.OFFLINE; - if (PresenceUpdated != null) - PresenceUpdated(this, dpuea); - } - else - { - if (!message["d"]["guild_id"].IsNullOrEmpty()) //if this is null or empty, that means this pertains to friends list - { - if (!message["d"]["user"]["username"].IsNullOrEmpty() && !message["d"]["user"]["id"].IsNullOrEmpty()) - { - DebugLogger.Log($"User {message["d"]["user"]["username"]} ({message["d"]["user"]["id"].ToString()}) doesn't exist in server {server.Name} ({server.ID}) no problemo. Creating/adding", MessageLevel.Debug); - DiscordMember memeber = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); - memeber.parentclient = this; - memeber.SetPresence(message["d"]["status"].ToString()); - memeber.Parent = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - - if (message["d"]["game"].IsNullOrEmpty()) - { - dpuea.Game = ""; - memeber.CurrentGame = null; - } - else - { - dpuea.Game = message["d"]["game"]["name"].ToString(); - memeber.CurrentGame = dpuea.Game; - if (message["d"]["game"]["type"].ToObject() == 1) - { - user.Streaming = true; - if (message["d"]["game"]["url"].ToString() != null) - user.StreamURL = message["d"]["game"]["url"].ToString(); - } - } - - if (message["d"]["status"].ToString() == "online") - dpuea.Status = DiscordUserStatus.ONLINE; - else if (message["d"]["status"].ToString() == "idle") - dpuea.Status = DiscordUserStatus.IDLE; - else if (message["d"]["status"].ToString() == null || message["d"]["status"].ToString() == "offline") - dpuea.Status = DiscordUserStatus.OFFLINE; - - memeber.Parent.AddMember(memeber); - } - } - } - } - } - } - - /// - /// Deletes a message with a specified ID. - /// This method will only work if the message was sent since the bot has ran. - /// - /// - public void DeleteMessage(string id) - { - var message = MessageLog[id]; - if (message != null) - SendDeleteRequest(message); - } - - /// - /// Deletes a specified DiscordMessage. - /// - /// - public void DeleteMessage(DiscordMessage message) - { - SendDeleteRequest(message); - } - - //public void DeletePrivateMessage(DiscordMessage message) - //{ - // SendDeleteRequest(message, true); - //} - - /// - /// Deletes all messages made by the bot since running. - /// - /// A count of messages deleted. - public int DeleteAllMessages() - { - int count = 0; - - foreach (var kvp in MessageLog) - { - if (kvp.Value.Author.ID == Me.ID) - { - SendDeleteRequest(kvp.Value); - count++; - } - } - return count; - } - - /// - /// Deletes the specified number of messages in a given channel. - /// Thank you to Siegen for this idea/method! - /// - /// The channel to delete messages in. - /// The amount of messages to delete (max 100) - /// The count of messages deleted. - public int DeleteMultipleMessagesInChannel(DiscordChannel channel, int count) - { - if (count > 100) - count = 100; - - int __count = 0; - - var messages = GetMessageHistory(channel, count, null, null); - - messages.ForEach(x => - { - if (x.channel.ID == channel.ID) - { - SendDeleteRequest(x); - __count++; - } - }); - - return __count; - } - - /// - /// - /// - /// - /// - /// - /// - public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string username, bool caseSensitive) - { - if (string.IsNullOrEmpty(username)) - throw new ArgumentException("Argument given for username was null/empty."); - if (channel != null) - { - if (channel.GetType() == typeof(DiscordChannel)) //regular channel - { - DiscordMember foundMember = ((DiscordChannel)channel).Parent.GetMemberByUsername(username, caseSensitive); - if (foundMember != null) - { - return foundMember; - } - else - { - DebugLogger.Log("Error in GetMemberFromChannel: foundMember was null!", MessageLevel.Error); - } - } - else if (channel.GetType() == typeof(DiscordPrivateChannel)) - { - return ((DiscordPrivateChannel)channel).Recipient; - } - } - else - { - DebugLogger.Log("Error in GetMemberFromChannel: channel was null!", MessageLevel.Error); - } - return null; - } - - /// - /// - /// - /// - /// - /// - public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string id) - { - if (channel != null) - { - if (channel.GetType() == typeof(DiscordChannel)) //regular - { - DiscordMember foundMember = ((DiscordChannel)channel).Parent.GetMemberByKey(id); - if (foundMember != null) - return foundMember; - else - { - DebugLogger.Log($"Error in GetMemberFromChannel: foundMember was null! ID: {id}", MessageLevel.Error); - } - } - else if (channel.GetType() == typeof(DiscordPrivateChannel)) - { - return ((DiscordPrivateChannel)channel).Recipient; - } - } - else - { - DebugLogger.Log("Error in GetMemberFromChannel: channel was null!", MessageLevel.Error); - } - return null; - } - - /// - /// you probably shouldn't use this. - /// - /// - /// - public DiscordChannel GetChannelByName(string channelName) - { - try - { - return ServersList.Find(x => x.Channels.Find(y => y.Name.ToLower() == channelName.ToLower()) != null).Channels.Find(x => x.Name.ToLower() == channelName.ToLower()); - } - catch - { - return null; - } - } - - /// - /// - /// - /// - /// - public DiscordChannel GetChannelByID(long id) - { - return ServersList.Find(x => x.Channels.Find(y => y.ID == id.ToString()) != null).Channels.Find(z => z.ID == id.ToString()); - } - - /// - /// (Client account only) accepts an invite to a server. - /// - /// The ID of the invite you want to accept. This is NOT the full URL of the invite - public void AcceptInvite(string inviteID) - { - if (!IsBotAccount) - { - if (inviteID.StartsWith("http://")) - inviteID = inviteID.Substring(inviteID.LastIndexOf('/') + 1); - - string url = Endpoints.BaseAPI + Endpoints.Invite + $"/{inviteID}"; - try - { - var result = WebWrapper.Post(url, token, "", true); - DebugLogger.Log("Accept invite result: " + result.ToString()); - } - catch (Exception ex) - { - DebugLogger.Log($"Error accepting invite: {ex.Message}", MessageLevel.Error); - } - } - else - throw new InvalidOperationException("Bot accounts can't accept invites normally! Please use the OAuth flow to add bots to servers you have the \"Manage Server\" permission in."); - } - - /// - /// - /// - /// The last DiscordMessage sent - public DiscordMessage GetLastMessageSent() - { - foreach (var message in MessageLog) - { - if (message.Value.Author.ID == Me.ID) - { - return message.Value; - } - } - return null; - } - - /// - /// - /// - /// - /// The last DiscordMessage sent in the given channel - public DiscordMessage GetLastMessageSent(DiscordChannel inChannel) - { - foreach (var message in MessageLog) - { - if (message.Value.Author.ID == Me.ID && message.Value.channel.ID == inChannel.ID) - { - return message.Value; - } - } - return null; - } - - /// - /// If you screwed up, you can use this method to edit a given message. This sends out an http patch request with a replacement message - /// - /// The ID of the message you want to edit. - /// What you want the text to be edited to. - /// The channel the message is in - /// the new and improved DiscordMessage object. - public DiscordMessage EditMessage(string MessageID, string replacementMessage, DiscordChannel channel) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Messages + $"/{MessageID}"; - try - { - string replacement = JsonConvert.SerializeObject( - new - { - content = replacementMessage, - mentions = new string[0] - } - ); - JObject result = JObject.Parse(WebWrapper.Patch(url, token, replacement)); - - DiscordMessage m = new DiscordMessage - { - RawJson = result, - Attachments = result["attachments"].ToObject(), - Author = channel.Parent.GetMemberByKey(result["author"]["id"].ToString()), - TypeOfChannelObject = channel.GetType(), - channel = channel, - Content = result["content"].ToString(), - ID = result["id"].ToString(), - timestamp = result["timestamp"].ToObject() - }; - return m; - } - catch (Exception ex) - { - DebugLogger.Log("Exception ocurred while editing: " + ex.Message, MessageLevel.Error); - } - - return null; - } - - private void SendDeleteRequest(DiscordMessage message) - { - string url; - //if(!user) - url = Endpoints.BaseAPI + Endpoints.Channels + $"/{message.channel.ID}" + Endpoints.Messages + $"/{message.ID}"; - //else - //url = Endpoints.BaseAPI + Endpoints.Channels + $"/{message.channel.id}" + Endpoints.Messages + $"/{message.id}"; - try - { - var result = WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Exception ocurred while deleting message (ID: {message.ID}): " + ex.Message, MessageLevel.Error); - } - } - - private DiscordMessage FindInMessageLog(ID id) - { - foreach (var message in MessageLog) - if (message.Key == id) - return message.Value; - - return null; - } - - private void MessageUpdateEvents(JObject message) - { - try - { - DiscordServer pserver = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); - DiscordChannel pchannel = pserver.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - if (pchannel != null) - { - if (message["d"]["author"] != null) - { - var toRemove = FindInMessageLog(message["d"]["id"].ToString()); - if (toRemove == null) - return; //No message exists - var jsonToEdit = toRemove.RawJson; - jsonToEdit["d"]["content"].Replace(JToken.FromObject(message["d"]["content"].ToString())); - if (MessageEdited != null) - MessageEdited(this, new DiscordMessageEditedEventArgs - { - Author = pserver.GetMemberByKey(message["d"]["author"]["id"].ToString()), - Channel = pchannel, - MessageText = message["d"]["content"].ToString(), - MessageType = DiscordMessageType.CHANNEL, - MessageEdited = new DiscordMessage - { - Author = pserver.GetMemberByKey(message["d"]["author"]["id"].ToString()), - Content = toRemove.Content, - Attachments = message["d"]["attachments"].ToObject(), - channel = pserver.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()), - RawJson = message, - ID = message["d"]["id"].ToString(), - timestamp = message["d"]["timestamp"].ToObject(), - }, - EditedTimestamp = message["d"]["edited_timestamp"].ToObject() - }); - MessageLog.Remove(message["d"]["id"].ToString()); - - DiscordMessage newMessage = toRemove; - newMessage.Content = jsonToEdit["d"]["content"].ToString(); - MessageLog.Add(message["d"]["id"].ToString(), newMessage); - } - else //I know they say assume makes an ass out of you and me...but we're assuming it's Discord's weird auto edit of a just URL message - { - if (URLMessageAutoUpdate != null) - { - DiscordURLUpdateEventArgs asdf = new DiscordURLUpdateEventArgs(); //I'm running out of clever names and should probably split these off into different internal voids soon... - asdf.ID = message["d"]["id"].ToString(); - asdf.Channel = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null).Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - foreach (var embed in message["d"]["embeds"]) - { - DiscordEmbeds temp = new DiscordEmbeds(); - temp.URL = embed["url"].ToString(); - temp.Description = embed["description"].ToString(); - try - { - temp.ProviderName = embed["provider"]["name"] == null ? null : embed["provider"]["name"].ToString(); - temp.ProviderURL = embed["provider"]["url"].ToString(); - } - catch { }//noprovider - temp.Title = embed["title"].ToString(); - temp.Type = embed["type"].ToString(); - asdf.Embeds.Add(temp); - } - URLMessageAutoUpdate(this, asdf); - } - } - } - else - { - DebugLogger.Log("Couldn't find channel!", MessageLevel.Critical); - } - } - catch (Exception ex) - { - DebugLogger.Log($"Exception during MessageUpdateEvents.\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Critical); - } - } - - private DiscordChannel GetDiscordChannelByID(string id) - { - DiscordChannel returnVal = new DiscordChannel { ID = "-1" }; - ServersList.ForEach(x => - { - x.Channels.ForEach(y => - { - if (y.ID == id) - returnVal = y; - }); - }); - if (returnVal.ID != "-1") - return returnVal; - else - return null; - } - - private void MessageCreateEvents(JObject message) - { - //try - //{ - string tempChannelID = message["d"]["channel_id"].ToString(); - - //DiscordServer foundServerChannel = ServersList.Find(x => x.channels.Find(y => y.id == tempChannelID) != null); - DiscordChannel potentialChannel = GetDiscordChannelByID(message["d"]["channel_id"].ToString()); - if (potentialChannel == null) //private message create - { - if (message["d"]["author"]["id"].ToString() != Me.ID) - { - var foundPM = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - var dpmea = new DiscordPrivateMessageEventArgs - { - Channel = foundPM, - Message = message["d"]["content"].ToString(), - Attachments = message["d"]["attachments"].AsEnumerable().Select(m => m.ToObject()).ToArray() - }; - var tempMember = new DiscordMember(this) - { - Username = message["d"]["author"]["username"].ToString(), - ID = message["d"]["author"]["id"].ToString() - }; - dpmea.Author = tempMember; - tempMember.parentclient = this; - - dpmea.RawJson = message; - - PrivateMessageReceived?.Invoke(this, dpmea); - } - else - { - //if (DebugMessageReceived != null) - // DebugMessageReceived(this, new DiscordDebugMessagesEventArgs { message = "Ignoring MESSAGE_CREATE for private channel for message sent from this client." }); - } - } - else - { - DiscordMessageEventArgs dmea = new DiscordMessageEventArgs(); - dmea.RawJson = message; - dmea.Channel = potentialChannel; - - dmea.MessageText = message["d"]["content"].ToString(); - - DiscordMember tempMember = null; - tempMember = potentialChannel.Parent.GetMemberByKey(message["d"]["author"]["id"].ToString()); - if (tempMember == null) - { - tempMember = JsonConvert.DeserializeObject(message["author"].ToString()); - tempMember.parentclient = this; - tempMember.Parent = potentialChannel.Parent; - - potentialChannel.Parent.AddMember(tempMember); - } - - dmea.Author = tempMember; - - DiscordMessage m = new DiscordMessage(); - m.Author = dmea.Author; - m.channel = dmea.Channel; - m.TypeOfChannelObject = dmea.Channel.GetType(); - m.Content = dmea.MessageText; - m.ID = message["d"]["id"].ToString(); - m.RawJson = message; - m.timestamp = DateTime.Now; - dmea.Message = m; - if (!message["d"]["attachments"].IsNullOrEmpty()) - { - List tempList = new List(); - foreach (var attachment in message["d"]["attachments"]) - { - tempList.Add(JsonConvert.DeserializeObject(attachment.ToString())); - } - m.Attachments = tempList.ToArray(); - } - - if (!message["d"]["mentions"].IsNullOrEmpty()) - { - JArray mentionsAsArray = JArray.Parse(message["d"]["mentions"].ToString()); - foreach (var mention in mentionsAsArray) - { - string id = mention["id"].ToString(); - if (id.Equals(Me.ID)) - { - if (MentionReceived != null) - MentionReceived(this, dmea); - } - } - } - - KeyValuePair toAdd = new KeyValuePair(message["d"]["id"].ToString(), m); - MessageLog.Add(message["d"]["id"].ToString(), m); - - if (MessageReceived != null) - MessageReceived(this, dmea); - } - //} - //catch (Exception ex) - //{ - // DebugLogger.Log("Error ocurred during MessageCreateEvents: " + ex.Message, MessageLevel.Error); - //} - } - - private void ChannelCreateEvents(JObject message) - { - if (message["d"]["is_private"].ToString().ToLower() == "false") - { - var foundServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - if (foundServer != null) - { - DiscordChannel tempChannel = new DiscordChannel(); - tempChannel.Client = this; - tempChannel.Name = message["d"]["name"].ToString(); - tempChannel.Type = message["d"]["type"].ToObject(); - if (tempChannel.Type == ChannelType.Voice && !message["d"]["bitrate"].IsNullOrEmpty()) - tempChannel.Bitrate = message["d"]["bitrate"].ToObject(); - - tempChannel.ID = message["d"]["id"].ToString(); - tempChannel.Parent = foundServer; - foundServer.Channels.Add(tempChannel); - DiscordChannelCreateEventArgs fae = new DiscordChannelCreateEventArgs(); - fae.ChannelCreated = tempChannel; - fae.ChannelType = DiscordChannelCreateType.CHANNEL; - if (ChannelCreated != null) - ChannelCreated(this, fae); - } - } - else - { - DiscordPrivateChannel tempPrivate = new DiscordPrivateChannel(); - tempPrivate.Client = this; - tempPrivate.ID = message["d"]["id"].ToString(); - DiscordMember recipient = ServersList.Find(x => x.GetMemberByKey(message["d"]["recipient"]["id"].ToString()) != null).GetMemberByKey(message["d"]["recipient"]["id"].ToString()); - tempPrivate.Recipient = recipient; - PrivateChannels.Add(tempPrivate); - DiscordPrivateChannelEventArgs fak = new DiscordPrivateChannelEventArgs { ChannelType = DiscordChannelCreateType.PRIVATE, ChannelCreated = tempPrivate }; - if (PrivateChannelCreated != null) - PrivateChannelCreated(this, fak); - } - } - - #endregion Message Received Crap.. - - private string GetGatewayUrl() - { - if (token == null) - throw new NullReferenceException("token was null!"); - - //i'm ashamed of myself for this but i'm tired - tryAgain: - string url = Endpoints.BaseAPI + Endpoints.Gateway; - if (V4Testing) - url = "https://ptb.discordapp.com/api/gateway"; - try - { - string gateway = JObject.Parse(WebWrapper.Get(url, token))["url"].ToString(); - if (!string.IsNullOrEmpty(gateway)) - { - return gateway + (V4Testing ? "?encoding=json&v=4" : ""); - } - else - throw new NullReferenceException("Failed to retrieve Gateway urL!"); - } - catch (UnauthorizedAccessException) //bad token - { - DebugLogger.Log("Got 401 from Discord. Token bad, deleting and retrying login..."); - if (File.Exists(((uint)StrippedEmail.GetHashCode()) + ".cache")) - { - File.Delete(((uint)StrippedEmail.GetHashCode()) + ".cache"); - } - SendLoginRequest(); - goto tryAgain; - } - catch (Exception ex) - { - DebugLogger.Log("Exception ocurred while retrieving Gateway URL: " + ex.Message, MessageLevel.Error); - return null; - } - } - - /// - /// - /// - /// - /// - public DiscordServer GetServerChannelIsIn(DiscordChannel channel) - { - return ServersList.Find(x => x.Channels.Find(y => y.ID == channel.ID) != null); - } - - /// - /// Deletes a specified Discord channel given you have the permission. - /// - /// The DiscordChannel object to delete - public void DeleteChannel(DiscordChannel channel) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log("Exception ocurred while deleting channel: " + ex.Message, MessageLevel.Error); - } - } - - /// - /// Creates either a text or voice channel in a DiscordServer given a name. Given you have the permission of course. - /// - /// The server to create the channel in. - /// The name of the channel (will automatically be lowercased if text) - /// True if you want the channel to be a voice channel. - /// The newly created DiscordChannel - public DiscordChannel CreateChannel(DiscordServer server, string ChannelName, bool voice) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{server.ID}" + Endpoints.Channels; - var reqJson = JsonConvert.SerializeObject(new { name = ChannelName, type = voice ? "voice" : "text" }); - try - { - var result = JObject.Parse(WebWrapper.Post(url, token, reqJson)); - if (result != null) - { - DiscordChannel dc = new DiscordChannel - { - Client = this, - Name = result["name"].ToString(), - ID = result["id"].ToString(), - Type = result["type"].ToObject(), - Private = result["is_private"].ToObject(), - }; - if (!result["topic"].IsNullOrEmpty()) - dc.Topic = result["topic"].ToString(); - if (dc.Type == ChannelType.Voice && !result["bitrate"].IsNullOrEmpty()) - dc.Bitrate = result["bitrate"].ToObject(); - - server.Channels.Add(dc); - return dc; - } - } - catch (Exception ex) - { - DebugLogger.Log("Exception ocurred while creating channel: " + ex.Message, MessageLevel.Error); - } - return null; - } - - /// - /// Creates an empty guild with only this client in it given the following name. - /// Unknown if works on bot accounts or not. - /// - /// The name of the guild you wish to create. - /// the created DiscordServer - public DiscordServer CreateGuild(string GuildName) - { - string createGuildUrl = Endpoints.BaseAPI + Endpoints.Guilds; - string req = JsonConvert.SerializeObject(new { name = GuildName }); - - try - { - var response = JObject.Parse(WebWrapper.Post(createGuildUrl, token, req)); - if (response != null) - { - DiscordServer server = new DiscordServer(); - server.JoinedAt = response["joined_at"].ToObject(); - server.ID = response["id"].ToString(); - server.Name = response["name"].ToString(); - server.parentclient = this; - - string channelGuildUrl = createGuildUrl + $"/{server.ID}" + Endpoints.Channels; - var channelRespone = JArray.Parse(WebWrapper.Get(channelGuildUrl, token)); - foreach (var item in channelRespone.Children()) - { - server.Channels.Add(new DiscordChannel - { - Client = this, - Name = item["name"].ToString(), - ID = item["id"].ToString(), - Topic = item["topic"].ToString(), - Private = item["is_private"].ToObject(), - Type = item["type"].ToObject() - }); - } - - server.AddMember(Me); - server.Owner = server.GetMemberByKey(response["owner_id"].ToString()); - if (server.Owner == null) - DebugLogger.Log("Owner is null in CreateGuild!", MessageLevel.Critical); - - ServersList.Add(server); - return server; - } - } - catch (Exception ex) - { - DebugLogger.Log("Exception ocurred while creating guild: " + ex.Message, MessageLevel.Error); - } - return null; - } - - /// - /// Edits the name of the guild, given you have the permission. - /// - /// The guild's name you wish to edit. - /// The new guild name. - public void EditGuildName(DiscordServer guild, string NewGuildName) - { - string editGuildUrl = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}"; - var newNameJson = JsonConvert.SerializeObject(new { name = NewGuildName }); - try - { - WebWrapper.Patch(editGuildUrl, token, newNameJson); - } - catch (Exception ex) - { - DebugLogger.Log($"Exception ocurred while editing guild ({guild.Name}) name: " + ex.Message, MessageLevel.Error); - } - } - - /// - /// Assigns a specified role to a member, given you have the permission. - /// - /// The guild you and the user are in. - /// The role you wish to assign them. - /// The member you wish to assign the role to. - public void AssignRoleToMember(DiscordServer guild, DiscordRole role, DiscordMember member) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Members + $"/{member.ID}"; - string message = JsonConvert.SerializeObject(new { roles = new string[] { role.ID } }); - try - { - WebWrapper.Patch(url, token, message); - } - catch (Exception ex) - { - DebugLogger.Log($"Exception ocurred while assigning role ({role.Name}) to member ({member.Username}): " - + ex.Message, MessageLevel.Error); - } - } - - /// - /// Assigns the specified roles to a member, given you have the permission. - /// - /// The guild you and the user are in. - /// The roles you wish to assign them. - /// The member you wish to assign the role to. - public void AssignRoleToMember(DiscordServer guild, List roles, DiscordMember member) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Members + $"/{member.ID}"; - List rolesAsIds = new List(); - roles.ForEach(x => rolesAsIds.Add(x.ID)); - string message = JsonConvert.SerializeObject(new { roles = rolesAsIds.ToArray() }); - try - { - WebWrapper.Patch(url, token, message); - } - catch (Exception ex) - { - DebugLogger.Log($"Exception ocurred while assigning {roles.Count} role(s) to member ({member.Username}): " - + ex.Message, MessageLevel.Error); - } - } - - /// - /// Creates and invite to the given channel. - /// - /// - /// The invite's id. - public string CreateInvite(DiscordChannel channel) - { - string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{channel.ID}" + Endpoints.Invites; - try - { - var resopnse = JObject.Parse(WebWrapper.Post(url, token, "{\"validate\":\"\"}")); - if (resopnse != null) - { - return resopnse["code"].ToString(); - } - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while creating invite for channel {channel.Name}: {ex.Message}", MessageLevel.Error); - } - return null; - } - - /// - /// Deletes an invite by id - /// - /// The ID of the invite you wish to delete. - public void DeleteInvite(string id) - { - string url = Endpoints.BaseAPI + Endpoints.Invites + $"/{id}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while deleting invite: {ex.Message}", MessageLevel.Error); - } - } - - /// - /// Just prepends https://discord.gg/ to a given invite :) - /// - /// - /// A full invite URL. - public string MakeInviteURLFromCode(string id) => "https://discord.gg/" + id; - - /// - /// Runs the websocket connection for the client hooking up the appropriate events. - /// - /// If true, DiscordSharp will connect using the .Net Framework's built-in WebSocketClasses. - /// Please do not use this on Mono or versions of Windows below 8/8.1 - public void Connect(bool useDotNetWebsocket = false) - { - CurrentGatewayURL = GetGatewayUrl(); - if (string.IsNullOrEmpty(CurrentGatewayURL)) - { - DebugLogger.Log("Gateway URL was null or empty?!", MessageLevel.Critical); - return; - } - DebugLogger.Log("Gateway retrieved: " + CurrentGatewayURL); - - if (useDotNetWebsocket) - { - ws = new NetWebSocket(CurrentGatewayURL); - DebugLogger.Log("Using the built-in .Net websocket.."); - } - else - { - ws = new WebSocketSharpSocket(CurrentGatewayURL); - DebugLogger.Log("Using WebSocketSharp websocket.."); - } - - ws.MessageReceived += (sender, e) => - { - var message = new JObject(); - try - { - message = JObject.Parse(e.Message); - } - catch (Exception ex) - { - DebugLogger.Log($"MessageReceived Error: {ex.Message}\n\n```{e.Message}\n```\n", MessageLevel.Error); - } - - if (EnableVerboseLogging) - if (message["t"].ToString() != "READY") - DebugLogger.Log(message.ToString(), MessageLevel.Unecessary); - - if (!message["t"].IsNullOrEmpty()) //contains a t parameter used for client events. - ClientPacketReceived(message); - else - MiscellaneousOpcodes(message); - - if (!message["s"].IsNullOrEmpty()) - Sequence = message["s"].ToObject(); - }; - ws.SocketOpened += (sender, e) => - { - SendIdentifyPacket(); - SocketOpened?.Invoke(this, null); - }; - ws.SocketClosed += (sender, e) => - { - DiscordSocketClosedEventArgs scev = new DiscordSocketClosedEventArgs(); - scev.Code = e.Code; - scev.Reason = e.Reason; - scev.WasClean = e.WasClean; - SocketClosed?.Invoke(this, scev); - - if (Autoconnect && !e.WasClean) - { - PerformReconnection(); - } - }; - ws.Connect(); - DebugLogger.Log("Connecting.."); - } - - private void MiscellaneousOpcodes(JObject message) - { - switch (message["d"].ToObject()) - { - case Opcodes.INVALIDATE_SESSION: - // TODO: the session was invalidated and a full reconnection must be performed. - DebugLogger.Log($"The session was invalidated. ", MessageLevel.Critical); - break; - } - } - - private void PerformReconnection() - { - string resumeJson = JsonConvert.SerializeObject(new - { - op = Opcodes.RESUME, - d = new - { - seq = Sequence, - token = DiscordClient.token, - session_id = SessionID - } - }); - } - - private void ClientPacketReceived(JObject message) - { - switch (message["t"].ToString()) - { - case ("READY"): - Sequence = message["s"].ToObject(); - DiscordGatewayVersion = message["d"]["v"].ToObject(); - HeartbeatInterval = message["d"]["heartbeat_interval"].ToObject(); - BeginHeartbeatTask(); - if (WriteLatestReady) - using (var sw = new StreamWriter("READY_LATEST.txt")) - sw.Write(message); - Me = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); - Me.parentclient = this; - IsBotAccount = message["d"]["user"]["bot"].IsNullOrEmpty() ? false : message["d"]["user"]["bot"].ToObject(); - ClientPrivateInformation.Avatar = Me.Avatar; - ClientPrivateInformation.Username = Me.Username; - GetChannelsList(message); - SessionID = message["d"]["session_id"].ToString(); - - //TESTING - string[] guildID = new string[ServersList.Count]; - for (int i = 0; i < guildID.Length; i++) - guildID[i] = ServersList[i].ID; - - if (RequestAllUsersOnStartup) - { - string wsChunkTest = JsonConvert.SerializeObject(new - { - op = 8, - d = new - { - guild_id = guildID, - query = "", - limit = 0 - } - }); - ws.Send(wsChunkTest); - } - - ReadyComplete = true; - - Task.Run(async () => - { - await Task.Delay(3000); - Connected?.Invoke(this, new DiscordConnectEventArgs { User = Me }); - }); //fire and forget waiting of up to 3 seconds for guilds to become available. - break; - - case ("GUILD_MEMBERS_CHUNK"): - GuildMemberChunkEvents(message); - break; - - case ("GUILD_MEMBER_REMOVE"): - GuildMemberRemoveEvents(message); - break; - - case ("GUILD_MEMBER_ADD"): - GuildMemberAddEvents(message); - break; - - case ("GUILD_DELETE"): - GuildDeleteEvents(message); - break; - - case ("GUILD_CREATE"): - GuildCreateEvents(message); - break; - - case ("GUILD_MEMBER_UPDATE"): - GuildMemberUpdateEvents(message); - break; - - case ("GUILD_UPDATE"): - GuildUpdateEvents(message); - break; - - case ("GUILD_ROLE_DELETE"): - GuildRoleDeleteEvents(message); - break; - - case ("GUILD_ROLE_UPDATE"): - GuildRoleUpdateEvents(message); - break; - - case ("PRESENCE_UPDATE"): - PresenceUpdateEvents(message); - break; - - case ("MESSAGE_UPDATE"): - MessageUpdateEvents(message); - break; - - case ("TYPING_START"): - DiscordServer server = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); - if (server != null) - { - DiscordChannel channel = server.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - DiscordMember uuser = server.GetMemberByKey(message["d"]["user_id"].ToString()); - if (UserTypingStart != null) - UserTypingStart(this, new DiscordTypingStartEventArgs { user = uuser, Channel = channel, Timestamp = int.Parse(message["d"]["timestamp"].ToString()) }); - } - break; - - case ("MESSAGE_CREATE"): - MessageCreateEvents(message); - break; - - case ("CHANNEL_CREATE"): - ChannelCreateEvents(message); - break; - - case ("VOICE_STATE_UPDATE"): - VoiceStateUpdateEvents(message); - break; - - case ("VOICE_SERVER_UPDATE"): - VoiceServerUpdateEvents(message); - break; - - case ("MESSAGE_DELETE"): - MessageDeletedEvents(message); - break; - - case ("USER_UPDATE"): - UserUpdateEvents(message); - break; - - case ("CHANNEL_UPDATE"): - ChannelUpdateEvents(message); - break; - - case ("CHANNEL_DELETE"): - ChannelDeleteEvents(message); - break; - - case ("GUILD_BAN_ADD"): - GuildMemberBannedEvents(message); - break; - - case ("GUILD_BAN_REMOVE"): - GuildMemberBanRemovedEvents(message); - break; - - case ("MESSAGE_ACK"): //ignore this message, it's irrelevant - break; - - default: - if (UnknownMessageTypeReceived != null) - UnknownMessageTypeReceived(this, new UnknownMessageEventArgs { RawJson = message }); - break; - } - } - - private void SendIdentifyPacket() - { - string initJson = JsonConvert.SerializeObject(new - { - op = 2, - d = new - { - v = 4, - token = token, - /*large_threshold = 50,*/ - properties = DiscordProperties - } - }); - - DebugLogger.Log("Sending initJson ( " + initJson + " )"); - - ws.Send(initJson); - } - - private void BeginHeartbeatTask() - { - KeepAliveTaskTokenSource = new CancellationTokenSource(); - KeepAliveTaskToken = KeepAliveTaskTokenSource.Token; - KeepAliveTask = new Task(() => - { - while (true) - { - DebugLogger.Log("Hello from inside KeepAliveTask!"); - Thread.Sleep(HeartbeatInterval); - KeepAlive(); - } - }, KeepAliveTaskToken); - KeepAliveTask.Start(); - DebugLogger.Log("Began keepalive task.."); - } - - private void GuildMemberChunkEvents(JObject message) - { - if (!message["d"]["members"].IsNullOrEmpty()) - { - DiscordServer inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - JArray membersAsArray = JArray.Parse(message["d"]["members"].ToString()); - foreach (var member in membersAsArray) - { - //if (GuildHasMemberWithID(inServer, member["user"]["id"].ToString())) - // continue; - DiscordMember _member = JsonConvert.DeserializeObject(member["user"].ToString()); - if (!member["user"]["roles"].IsNullOrEmpty()) - { - JArray rollsArray = JArray.Parse(member["user"]["roles"].ToString()); - if (rollsArray.Count > 0) - { - foreach (var rollID in rollsArray) - _member.Roles.Add(inServer.Roles.Find(x => x.ID == rollID.ToString())); - } - } - _member.Muted = member["mute"].ToObject(); - _member.Deaf = member["deaf"].ToObject(); - _member.Roles.Add(inServer.Roles.Find(x => x.Name == "@everyone")); - _member.Status = Status.Offline; - _member.parentclient = this; - _member.Parent = inServer; - inServer.AddMember(_member); - - ///Check private channels - DiscordPrivateChannel _channel = PrivateChannels.Find(x => x.user_id == _member.ID); - if (_channel != null) - { - DebugLogger.Log("Found user for private channel!", MessageLevel.Debug); - _channel.Recipient = _member; - } - } - } - } - - private void GuildMemberBanRemovedEvents(JObject message) - { - DiscordBanRemovedEventArgs e = new DiscordBanRemovedEventArgs(); - - e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - e.MemberStub = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); - - if (BanRemoved != null) - BanRemoved(this, e); - } - - private void GuildMemberBannedEvents(JObject message) - { - DiscordGuildBanEventArgs e = new DiscordGuildBanEventArgs(); - e.Server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - if (e.Server != null) - { - e.MemberBanned = e.Server.GetMemberByKey(message["d"]["user"]["id"].ToString()); - if (e.MemberBanned != null) - { - if (GuildMemberBanned != null) - GuildMemberBanned(this, e); - ServersList.Find(x => x.ID == e.Server.ID).RemoveMember(e.MemberBanned.ID); - } - else - { - DebugLogger.Log("Error in GuildMemberBannedEvents: MemberBanned is null, attempting internal index of removed members.", MessageLevel.Error); - e.MemberBanned = RemovedMembers.Find(x => x.ID == message["d"]["user"]["id"].ToString()); - if (e.MemberBanned != null) - { - if (GuildMemberBanned != null) - GuildMemberBanned(this, e); - } - else - { - DebugLogger.Log("Error in GuildMemberBannedEvents: MemberBanned is null, not even found in internal index!", MessageLevel.Error); - } - } - } - else - { - DebugLogger.Log("Error in GuildMemberBannedEvents: Server is null?!", MessageLevel.Error); - } - } - - private void VoiceServerUpdateEvents(JObject message) - { - // TODO VoiceClient is null when disconnecting from voice - if (VoiceClient == null) - { - return; - } - VoiceClient.VoiceEndpoint = message["d"]["endpoint"].ToString(); - VoiceClient.Token = message["d"]["token"].ToString(); - - VoiceClient.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - VoiceClient.Me = Me; - - VoiceClient.PacketReceived += (sender, e) => - { - if (AudioPacketReceived != null) - AudioPacketReceived(sender, e); - }; - - VoiceClient.DebugMessageReceived += (sender, e) => - { - if (VoiceClientDebugMessageReceived != null) - VoiceClientDebugMessageReceived(this, e); - }; - - ConnectToVoiceAsync(); - } - -#if NETFX4_5 - - private void ConnectToVoiceAsync() - { - VoiceClient.InitializeOpusEncoder(); - VoiceThread = new Thread(() => VoiceClient.Initiate()); - VoiceThread.Start(); - } - -#else - private Task ConnectToVoiceAsync() - { - VoiceClient.InitializeOpusEncoder(); - return Task.Factory.StartNew(() => VoiceClient.Initiate()); - } -#endif - - /// - /// Kicks a specified DiscordMember from the guild that's assumed from their - /// parent property. - /// - /// - public void KickMember(DiscordMember member) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{member.Parent.ID}" + Endpoints.Members + $"/{member.ID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error during KickMember\n\t{ex.Message}\n\t{ex.StackTrace}", MessageLevel.Error); - } - } - - /// - /// Bans a specified DiscordMember from the guild that's assumed from their - /// parent property. - /// - /// - /// The number of days the user should be banned for, or 0 for infinite. - public DiscordMember BanMember(DiscordMember member, int days = 0) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{member.Parent.ID}" + Endpoints.Bans + $"/{member.ID}"; - url += $"?delete-message-days={days}"; - try - { - WebWrapper.Put(url, token); - return member; - } - catch (Exception ex) - { - DebugLogger.Log($"Error during BanMember\n\t{ex.Message}\n\t{ex.StackTrace}", MessageLevel.Error); - return null; - } - } - - /// - /// Bans a specified DiscordMember from the guild that's assumed from their - /// parent property. - /// - /// - /// - /// - /// - public DiscordMember BanMember(DiscordMember member, DiscordServer serverOverride, int days = 0) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{serverOverride.ID}" + Endpoints.Bans + $"/{member.ID}"; - url += $"?delete-message-days={days}"; - try - { - WebWrapper.Put(url, token); - return member; - } - catch (Exception ex) - { - DebugLogger.Log($"Error during BanMember\n\t{ex.Message}\n\t{ex.StackTrace}", MessageLevel.Error); - return null; - } - } - - /// - /// Retrieves a DiscordMember List of members banned in the specified server. - /// - /// - /// Null if no permission. - public List GetBans(DiscordServer server) - { - List returnVal = new List(); - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{server.ID}" + Endpoints.Bans; - try - { - var __res = WebWrapper.Get(url, token); - var permissionCheck = JObject.Parse(__res); - { - if (!permissionCheck["message"].IsNullOrEmpty()) - return null; //no permission - } - JArray response = JArray.Parse(__res); - if (response != null && response.Count > 0) - { - DebugLogger.Log($"Ban count: {response.Count}"); - - foreach (var memberStub in response) - { - DiscordMember temp = JsonConvert.DeserializeObject(memberStub["user"].ToString()); - if (temp != null) - returnVal.Add(temp); - else - DebugLogger.Log($"memberStub[\"user\"] was null?! Username: {memberStub["user"]["username"].ToString()} ID: {memberStub["user"]["username"].ToString()}", MessageLevel.Error); - } - } - else - return returnVal; - } - catch (Exception ex) - { - DebugLogger.Log($"An error ocurred while retrieving bans for server \"{server.Name}\"\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", - MessageLevel.Error); - } - return returnVal; - } - - /// - /// Removes a ban on the user. - /// - /// The guild to lift the ban from. - /// The ID of the user to lift the ban. - public void RemoveBan(DiscordServer guild, string userID) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Bans + $"/{userID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error during RemoveBan\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Error); - } - } - - /// - /// Removes a ban on the user. - /// - /// The guild to lift the ban from. - /// The DiscordMember object of the user to lift the ban from, assuming you have it. - public void RemoveBan(DiscordServer guild, DiscordMember member) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Bans + $"/{member.ID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error during RemoveBan\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Error); - } - } - - /// - /// Echoes a received audio packet back. - /// - /// - public void EchoPacket(DiscordAudioPacket packet) - { - if (VoiceClient != null && ConnectedToVoice()) - VoiceClient.EchoPacket(packet).Wait(); - } - - /// - /// Connects to a given voice channel. - /// - /// The channel to connect to. - /// The voice configuration to use. If null, default values will be used. - /// Whether or not the client will connect muted. Defaults to false. - /// Whether or not the client will connect deaf. Defaults to false. - public void ConnectToVoiceChannel(DiscordChannel channel, DiscordVoiceConfig voiceConfig = null, bool clientMuted = false, bool clientDeaf = false) - { - if (channel.Type != ChannelType.Voice) - throw new InvalidOperationException($"Channel '{channel.ID}' is not a voice channel!"); - - if (ConnectedToVoice()) - DisconnectFromVoice(); - - if (VoiceClient == null) - { - if (voiceConfig == null) - { - VoiceClient = new DiscordVoiceClient(this, new DiscordVoiceConfig()); - } - else - VoiceClient = new DiscordVoiceClient(this, voiceConfig); - } - VoiceClient.Channel = channel; - VoiceClient.ErrorReceived += (sender, e) => - { - if (GetLastVoiceClientLogger != null) - { - GetLastVoiceClientLogger = VoiceClient.GetDebugLogger; - DisconnectFromVoice(); - } - }; - VoiceClient.UserSpeaking += (sender, e) => - { - if (UserSpeaking != null) - UserSpeaking(this, e); - }; - VoiceClient.VoiceConnectionComplete += (sender, e) => - { - if (VoiceClientConnected != null) - VoiceClientConnected(this, e); - }; - VoiceClient.QueueEmpty += (sender, e) => - { - VoiceQueueEmpty?.Invoke(this, e); - }; - - string joinVoicePayload = JsonConvert.SerializeObject(new - { - op = 4, - d = new - { - guild_id = channel.Parent.ID, - channel_id = channel.ID, - self_mute = clientMuted, - self_deaf = clientDeaf - } - }); - - ws.Send(joinVoicePayload); - } - - /// - /// Clears the internal message log cache - /// - /// The number of internal messages cleared. - public int ClearInternalMessageLog() - { - int totalCount = MessageLog.Count; - MessageLog.Clear(); - return totalCount; - } - - /// - /// Iterates through a server's members and removes offline users. - /// - /// - /// The amount of users cleared. - public int ClearOfflineUsersFromServer(DiscordServer server) - { - return server.ClearOfflineMembers(); - } - - /// - /// Also disposes - /// - public void DisconnectFromVoice() - { - string disconnectMessage = JsonConvert.SerializeObject(new - { - op = 4, - d = new - { - guild_id = VoiceClient != null && VoiceClient.Channel != null ? VoiceClient.Channel.Parent.ID : (object)null, - channel_id = (object)null, - self_mute = true, - self_deaf = false - } - }); - if (VoiceClient != null) - { - try - { - VoiceClient.Dispose(); - VoiceClient = null; - - ws.Send(disconnectMessage); - } - catch - { } - } - if (ws != null) - ws.Send(disconnectMessage); - VoiceClient = null; - if (VoiceThread != null) - VoiceThread.Abort(); - DebugLogger.Log($"Disconnected from voice. VoiceClient null: {VoiceClient == null}"); - } - - /// - /// - /// - /// The current VoiceClient or null. - public DiscordVoiceClient GetVoiceClient() - { - if (ConnectedToVoice() && VoiceClient != null) - return VoiceClient; - - return null; - } - - private void GuildMemberUpdateEvents(JObject message) - { - DiscordServer server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - - DiscordMember memberUpdated = server.GetMemberByKey(message["d"]["user"]["id"].ToString()); - if (memberUpdated != null) - { - memberUpdated.Username = message["d"]["user"]["username"].ToString(); - if (message["d"]["nick"] != null) - { - if (message["d"]["nick"].ToString() == null) - memberUpdated.Nickname = ""; //No nickname - else - memberUpdated.Nickname = message["d"]["nick"].ToString(); - } - - if (!message["d"]["user"]["avatar"].IsNullOrEmpty()) - memberUpdated.Avatar = message["d"]["user"]["avatar"].ToString(); - memberUpdated.Discriminator = message["d"]["user"]["discriminator"].ToString(); - memberUpdated.ID = message["d"]["user"]["id"].ToString(); - - foreach (var roles in message["d"]["roles"]) - { - memberUpdated.Roles.Add(server.Roles.Find(x => x.ID == roles.ToString())); - } - - server.AddMember(memberUpdated); - GuildMemberUpdated?.Invoke(this, new DiscordGuildMemberUpdateEventArgs { MemberUpdate = memberUpdated, RawJson = message, ServerUpdated = server }); - } - else - { - DebugLogger.Log("memberUpdated was null?!?!?!", MessageLevel.Debug); - } - } - - private void GuildRoleUpdateEvents(JObject message) - { - DiscordServer inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - DiscordRole roleUpdated = new DiscordRole - { - Name = message["d"]["role"]["name"].ToString(), - Position = message["d"]["role"]["position"].ToObject(), - Permissions = new DiscordPermission(message["d"]["role"]["permissions"].ToObject()), - Managed = message["d"]["role"]["managed"].ToObject(), - Hoist = message["d"]["role"]["hoist"].ToObject(), - Color = new Color(message["d"]["role"]["color"].ToObject().ToString("x")), - ID = message["d"]["role"]["id"].ToString(), - }; - - ServersList.Find(x => x.ID == inServer.ID).Roles.Remove(ServersList.Find(x => x.ID == inServer.ID).Roles.Find(y => y.ID == roleUpdated.ID)); - ServersList.Find(x => x.ID == inServer.ID).Roles.Add(roleUpdated); - - RoleUpdated?.Invoke(this, new DiscordGuildRoleUpdateEventArgs { RawJson = message, RoleUpdated = roleUpdated, InServer = inServer }); - } - - private void GuildRoleDeleteEvents(JObject message) - { - DiscordServer inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - DiscordRole deletedRole = inServer.Roles.Find(x => x.ID == message["d"]["role_id"].ToString()); - - try - { - ServersList.Find(x => x.ID == inServer.ID).Roles.Remove(ServersList.Find(x => x.ID == inServer.ID).Roles.Find(y => y.ID == deletedRole.ID)); - } - catch (Exception ex) - { - DebugLogger.Log($"Couldn't delete role with ID {message["d"]["role_id"].ToString()}! ({ex.Message})", MessageLevel.Critical); - } - - RoleDeleted?.Invoke(this, new DiscordGuildRoleDeleteEventArgs { DeletedRole = deletedRole, Guild = inServer, RawJson = message }); - } - - /// - /// Creates a default role in the specified guild. - /// - /// The guild to make the role in. - /// The newly created role. - public DiscordRole CreateRole(DiscordServer guild) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles; - - try - { - var result = JObject.Parse(WebWrapper.Post(url, token, "")); - - if (result != null) - { - DiscordRole d = new DiscordRole - { - Color = new Color(result["color"].ToObject().ToString("x")), - Hoist = result["hoist"].ToObject(), - ID = result["id"].ToString(), - Managed = result["managed"].ToObject(), - Name = result["name"].ToString(), - Permissions = new DiscordPermission(result["permissions"].ToObject()), - Position = result["position"].ToObject() - }; - - ServersList.Find(x => x.ID == guild.ID).Roles.Add(d); - return d; - } - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while creating role in guild {guild.Name}: {ex.Message}", MessageLevel.Error); - } - return null; - } - - /// - /// Edits a role with the new role information. - /// - /// The guild the role is in. - /// the new role. - /// The newly edited role returned from Discord. - public DiscordRole EditRole(DiscordServer guild, DiscordRole newRole) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles + $"/{newRole.ID}"; - string request = JsonConvert.SerializeObject( - new - { - color = decimal.Parse(newRole.Color.ToDecimal().ToString()), - hoist = newRole.Hoist, - name = newRole.Name, - permissions = newRole.Permissions.GetRawPermissions() - } - ); - - try - { - var result = JObject.Parse(WebWrapper.Patch(url, token, request)); - if (result != null) - { - DiscordRole d = new DiscordRole - { - Color = new Color(result["color"].ToObject().ToString("x")), - Hoist = result["hoist"].ToObject(), - ID = result["id"].ToString(), - Managed = result["managed"].ToObject(), - Name = result["name"].ToString(), - Permissions = new DiscordPermission(result["permissions"].ToObject()), - Position = result["position"].ToObject() - }; - - ServersList.Find(x => x.ID == guild.ID).Roles.Remove(d); - ServersList.Find(x => x.ID == guild.ID).Roles.Add(d); - return d; - } - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while editing role ({newRole.Name}): {ex.Message}", MessageLevel.Error); - } - - return null; - } - - /// - /// Deletes a specified role. - /// - /// The guild the role is in. - /// The role to delete. - public void DeleteRole(DiscordServer guild, DiscordRole role) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{guild.ID}" + Endpoints.Roles + $"/{role.ID}"; - try - { - WebWrapper.Delete(url, token); - } - catch (Exception ex) - { - DebugLogger.Log($"Error ocurred while deleting role ({role.Name}): {ex.Message}", MessageLevel.Error); - } - } - - private void GuildUpdateEvents(JObject message) - { - DiscordServer oldServer = ServersList.Find(x => x.ID == message["d"]["id"].ToString()); - DiscordServer newServer = oldServer.ShallowCopy(); - - newServer.Name = message["d"]["name"].ToString(); - newServer.ID = message["d"]["id"].ToString(); - newServer.parentclient = this; - newServer.Roles = new List(); - newServer.Region = message["d"]["region"].ToString(); - if (!message["d"]["icon"].IsNullOrEmpty()) - { - newServer.icon = message["d"]["icon"].ToString(); - } - if (!message["d"]["roles"].IsNullOrEmpty()) - { - foreach (var roll in message["d"]["roles"]) - { - DiscordRole t = new DiscordRole - { - Color = new DiscordSharp.Color(roll["color"].ToObject().ToString("x")), - Name = roll["name"].ToString(), - Permissions = new DiscordPermission(roll["permissions"].ToObject()), - Position = roll["position"].ToObject(), - Managed = roll["managed"].ToObject(), - ID = roll["id"].ToString(), - Hoist = roll["hoist"].ToObject() - }; - newServer.Roles.Add(t); - } - } - else - { - newServer.Roles = oldServer.Roles; - } - newServer.Channels = new List(); - if (!message["d"]["channels"].IsNullOrEmpty()) - { - foreach (var u in message["d"]["channels"]) - { - DiscordChannel tempSub = new DiscordChannel(); - tempSub.Client = this; - tempSub.ID = u["id"].ToString(); - tempSub.Name = u["name"].ToString(); - tempSub.Type = u["type"].ToObject(); - - if (!u["topic"].IsNullOrEmpty()) - tempSub.Topic = u["topic"].ToString(); - if (tempSub.Type == ChannelType.Voice && !u["bitrate"].IsNullOrEmpty()) - tempSub.Bitrate = u["bitrate"].ToObject(); - - tempSub.Parent = newServer; - List permissionoverrides = new List(); - foreach (var o in u["permission_overwrites"]) - { - DiscordPermissionOverride dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); - dpo.type = o["type"].ToObject(); - dpo.id = o["id"].ToString(); - - permissionoverrides.Add(dpo); - } - tempSub.PermissionOverrides = permissionoverrides; - - newServer.Channels.Add(tempSub); - } - } - else - { - newServer.Channels = oldServer.Channels; - } - if (!message["d"]["members"].IsNullOrEmpty()) - { - foreach (var mm in message["d"]["members"]) - { - DiscordMember member = JsonConvert.DeserializeObject(mm["user"].ToString()); - member.parentclient = this; - member.Parent = newServer; - - JArray rawRoles = JArray.Parse(mm["roles"].ToString()); - if (rawRoles.Count > 0) - { - foreach (var role in rawRoles.Children()) - { - member.Roles.Add(newServer.Roles.Find(x => x.ID == role.Value())); - } - } - else - { - member.Roles.Add(newServer.Roles.Find(x => x.Name == "@everyone")); - } - - newServer.AddMember(member); - } - } - else - { - newServer.Members = oldServer.Members; - } - if (!message["d"]["owner_id"].IsNullOrEmpty()) - { - newServer.Owner = newServer.GetMemberByKey(message["d"]["owner_id"].ToString()); - DebugLogger.Log($"Transferred ownership from user '{oldServer.Owner.Username}' to {newServer.Owner.Username}."); - } - ServersList.Remove(oldServer); - ServersList.Add(newServer); - DiscordServerUpdateEventArgs dsuea = new DiscordServerUpdateEventArgs { NewServer = newServer, OldServer = oldServer }; - GuildUpdated?.Invoke(this, dsuea); - } - - private void ChannelDeleteEvents(JObject message) - { - if (!message["d"]["recipient"].IsNullOrEmpty()) - { - //private channel removed - DiscordPrivateChannelDeleteEventArgs e = new DiscordPrivateChannelDeleteEventArgs(); - e.PrivateChannelDeleted = PrivateChannels.Find(x => x.ID == message["d"]["id"].ToString()); - if (e.PrivateChannelDeleted != null) - { - if (PrivateChannelDeleted != null) - PrivateChannelDeleted(this, e); - PrivateChannels.Remove(e.PrivateChannelDeleted); - } - else - { - DebugLogger.Log("Error in ChannelDeleteEvents: PrivateChannel is null!", MessageLevel.Error); - } - } - else - { - DiscordChannelDeleteEventArgs e = new DiscordChannelDeleteEventArgs { ChannelDeleted = GetChannelByID(message["d"]["id"].ToObject()) }; - DiscordServer server; - server = e.ChannelDeleted.Parent; - server.Channels.Remove(server.Channels.Find(x => x.ID == e.ChannelDeleted.ID)); - - if (ChannelDeleted != null) - ChannelDeleted(this, e); - } - } - - private void ChannelUpdateEvents(JObject message) - { - DiscordChannelUpdateEventArgs e = new DiscordChannelUpdateEventArgs(); - e.RawJson = message; - DiscordChannel oldChannel = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["id"].ToString()) != null).Channels.Find(x => x.ID == message["d"]["id"].ToString()); - e.OldChannel = oldChannel.ShallowCopy(); - DiscordChannel newChannel = oldChannel; - newChannel.Name = message["d"]["name"].ToString(); - if (!message["d"]["topic"].IsNullOrEmpty()) - newChannel.Topic = message["d"]["topic"].ToString(); - else - newChannel.Topic = oldChannel.Topic; - - newChannel.Private = message["d"]["is_private"].ToObject(); - - List permissionoverrides = new List(); - foreach (var o in message["d"]["permission_overwrites"]) - { - DiscordPermissionOverride dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); - dpo.type = o["type"].ToObject(); - dpo.id = o["id"].ToString(); - - permissionoverrides.Add(dpo); - } - newChannel.PermissionOverrides = permissionoverrides; - - e.NewChannel = newChannel; - - DiscordServer serverToRemoveFrom = ServersList.Find(x => x.Channels.Find(y => y.ID == newChannel.ID) != null); - newChannel.Parent = serverToRemoveFrom; - int indexOfServer = ServersList.IndexOf(serverToRemoveFrom); - serverToRemoveFrom.Channels.Remove(oldChannel); - serverToRemoveFrom.Channels.Add(newChannel); - - ServersList.RemoveAt(indexOfServer); - ServersList.Insert(indexOfServer, serverToRemoveFrom); - - if (ChannelUpdated != null) - ChannelUpdated(this, e); - } - - private void GuildDeleteEvents(JObject message) - { - DiscordGuildDeleteEventArgs e = new DiscordGuildDeleteEventArgs(); - e.Server = ServersList.Find(x => x.ID == message["d"]["id"].ToString()); - e.RawJson = message; - ServersList.Remove(e.Server); - if (GuildDeleted != null) - GuildDeleted(this, e); - } - - private void GuildCreateEvents(JObject message) - { - DiscordGuildCreateEventArgs e = new DiscordGuildCreateEventArgs(); - e.RawJson = message; - DiscordServer server = new DiscordServer(); - server.JoinedAt = message["d"]["joined_at"].ToObject(); - server.parentclient = this; - server.ID = message["d"]["id"].ToString(); - server.Name = message["d"]["name"].ToString(); - server.Members = new Dictionary(); - server.Channels = new List(); - server.Roles = new List(); - foreach (var roll in message["d"]["roles"]) - { - DiscordRole t = new DiscordRole - { - Color = new DiscordSharp.Color(roll["color"].ToObject().ToString("x")), - Name = roll["name"].ToString(), - Permissions = new DiscordPermission(roll["permissions"].ToObject()), - Position = roll["position"].ToObject(), - Managed = roll["managed"].ToObject(), - ID = roll["id"].ToString(), - Hoist = roll["hoist"].ToObject() - }; - server.Roles.Add(t); - } - foreach (var chn in message["d"]["channels"]) - { - DiscordChannel tempChannel = new DiscordChannel(); - tempChannel.Client = this; - tempChannel.ID = chn["id"].ToString(); - tempChannel.Type = chn["type"].ToObject(); - - if (!chn["topic"].IsNullOrEmpty()) - tempChannel.Topic = chn["topic"].ToString(); - if (tempChannel.Type == ChannelType.Voice && !chn["bitrate"].IsNullOrEmpty()) - tempChannel.Bitrate = chn["bitrate"].ToObject(); - - tempChannel.Name = chn["name"].ToString(); - tempChannel.Private = false; - tempChannel.PermissionOverrides = new List(); - tempChannel.Parent = server; - foreach (var o in chn["permission_overwrites"]) - { - if (tempChannel.Type == ChannelType.Voice) - continue; - DiscordPermissionOverride dpo = new DiscordPermissionOverride(o["allow"].ToObject(), o["deny"].ToObject()); - dpo.id = o["id"].ToString(); - - if (o["type"].ToString() == "member") - dpo.type = DiscordPermissionOverride.OverrideType.member; - else - dpo.type = DiscordPermissionOverride.OverrideType.role; - - tempChannel.PermissionOverrides.Add(dpo); - } - server.Channels.Add(tempChannel); - } - foreach (var mbr in message["d"]["members"]) - { - DiscordMember member = JsonConvert.DeserializeObject(mbr["user"].ToString()); - if (mbr["nick"] != null) - member.Nickname = mbr["nick"].ToString(); - - member.parentclient = this; - member.Parent = server; - - foreach (var rollid in mbr["roles"]) - { - member.Roles.Add(server.Roles.Find(x => x.ID == rollid.ToString())); - } - if (member.Roles.Count == 0) - member.Roles.Add(server.Roles.Find(x => x.Name == "@everyone")); - server.AddMember(member); - } - foreach (var voiceStateJSON in message["d"]["voice_states"]) - { - DiscordVoiceState voiceState = JsonConvert.DeserializeObject(voiceStateJSON.ToString()); - DiscordMember member = server.GetMemberByKey(voiceState.UserID); - - member.CurrentVoiceChannel = server.Channels.Find(x => x.ID == voiceState.ChannelID); - member.VoiceState = voiceState; - } - server.Owner = server.GetMemberByKey(message["d"]["owner_id"].ToString()); - e.Server = server; - - if (!message["d"]["unavailable"].IsNullOrEmpty() && message["d"]["unavailable"].ToObject() == false) - { - var oldServer = ServersList.Find(x => x.ID == server.ID); - if (oldServer != null && oldServer.Unavailable) - ServersList.Remove(oldServer); - - ServersList.Add(server); - - DebugLogger.Log($"Guild with ID {server.ID} ({server.Name}) became available."); - GuildAvailable?.Invoke(this, e); - return; - } - - ServersList.Add(server); - GuildCreated?.Invoke(this, e); - } - - private void GuildMemberAddEvents(JObject message) - { - DiscordGuildMemberAddEventArgs e = new DiscordGuildMemberAddEventArgs(); - e.RawJson = message; - e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - - DiscordMember existingMember = e.Guild.GetMemberByKey(message["d"]["user"]["id"].ToString()); - if (existingMember != null) - { - DiscordMember newMember = JsonConvert.DeserializeObject(message["d"]["user"].ToString()); - newMember.parentclient = this; - e.AddedMember = newMember; - newMember.Parent = e.Guild; - e.Roles = message["d"]["roles"].ToObject(); - e.JoinedAt = DateTime.Parse(message["d"]["joined_at"].ToString()); - - ServersList.Find(x => x == e.Guild).AddMember(newMember); - if (UserAddedToServer != null) - UserAddedToServer(this, e); - } - else - { - DebugLogger.Log($"Skipping guild member add because user already exists in server.", MessageLevel.Debug); - } - } - - private void GuildMemberRemoveEvents(JObject message) - { - DiscordGuildMemberRemovedEventArgs e = new DiscordGuildMemberRemovedEventArgs(); - DiscordMember removed = new DiscordMember(this); - removed.parentclient = this; - - List membersToRemove = new List(); - foreach (var server in ServersList) - { - if (server.ID != message["d"]["guild_id"].ToString()) - continue; - foreach (var member in server.Members) - { - if (member.Value.ID == message["d"]["user"]["id"].ToString()) - { - removed = member.Value; - membersToRemove.Add(removed); - RemovedMembers.Add(removed); - } - } - } - - foreach (var member in membersToRemove) - { - foreach (var server in ServersList) - { - try - { - server.RemoveMember(member.ID); - } - catch { } //oh, you mean useless? - } - } - e.MemberRemoved = removed; - e.Server = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - e.RawJson = message; - - if (UserRemovedFromServer != null) - UserRemovedFromServer(this, e); - } - - private void UserUpdateEvents(JObject message) - { - DiscordUserUpdateEventArgs e = new DiscordUserUpdateEventArgs(); - e.RawJson = message; - DiscordMember newMember = JsonConvert.DeserializeObject(message["d"].ToString()); - newMember.parentclient = this; - - DiscordMember oldMember = new DiscordMember(this); - oldMember.parentclient = this; - //Update members - foreach (var server in ServersList) - { - foreach (var member in server.Members) - { - if (member.Value.ID == newMember.ID) - { - oldMember = member.Value; - server.AddMember(newMember); - break; - } - } - } - - newMember.Parent = oldMember.Parent; - - if (!message["roles"].IsNullOrEmpty()) - { - JArray rawRoles = JArray.Parse(message["roles"].ToString()); - if (rawRoles.Count > 0) - { - foreach (var role in rawRoles.Children()) - { - newMember.Roles.Add(newMember.Parent.Roles.Find(x => x.ID == role.ToString())); - } - } - else - { - newMember.Roles.Add(newMember.Parent.Roles.Find(x => x.Name == "@everyone")); - } - } - - e.NewMember = newMember; - e.OriginalMember = oldMember; - UserUpdate?.Invoke(this, e); - } - - private void MessageDeletedEvents(JObject message) - { - DiscordMessageDeletedEventArgs e = new DiscordMessageDeletedEventArgs(); - e.DeletedMessage = FindInMessageLog(message["d"]["id"].ToString()); - - DiscordServer inServer; - inServer = ServersList.Find(x => x.Channels.Find(y => y.ID == message["d"]["channel_id"].ToString()) != null); - if (inServer == null) //dm delete - { - DiscordPrivateMessageDeletedEventArgs dm = new DiscordPrivateMessageDeletedEventArgs(); - dm.DeletedMessage = e.DeletedMessage; - dm.RawJson = message; - dm.Channel = PrivateChannels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - if (PrivateMessageDeleted != null) - PrivateMessageDeleted(this, dm); - } - else - { - e.Channel = inServer.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - e.RawJson = message; - } - - if (MessageDeleted != null) - MessageDeleted(this, e); - } - - private void VoiceStateUpdateEvents(JObject message) - { - var f = message["d"]["channel_id"]; - if (f.ToString() == null) - { - DiscordLeftVoiceChannelEventArgs le = new DiscordLeftVoiceChannelEventArgs(); - DiscordServer inServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - le.User = inServer.GetMemberByKey(message["d"]["user_id"].ToString()); - le.Guild = inServer; - le.RawJson = message; - - if (VoiceClient != null && VoiceClient.Connected) - VoiceClient.MemberRemoved(le.User); - if (UserLeftVoiceChannel != null) - UserLeftVoiceChannel(this, le); - return; - } - DiscordVoiceStateUpdateEventArgs e = new DiscordVoiceStateUpdateEventArgs(); - e.Guild = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString()); - DiscordMember memberToUpdate = e.Guild.GetMemberByKey(message["d"]["user_id"].ToString()); - if (memberToUpdate != null) - { - e.Channel = e.Guild.Channels.Find(x => x.ID == message["d"]["channel_id"].ToString()); - memberToUpdate.CurrentVoiceChannel = e.Channel; - if (!message["d"]["self_deaf"].IsNullOrEmpty()) - e.SelfDeaf = message["d"]["self_deaf"].ToObject(); - e.Deaf = message["d"]["deaf"].ToObject(); - if (!message["d"]["self_mute"].IsNullOrEmpty()) - e.SelfMute = message["d"]["self_mute"].ToObject(); - e.Mute = message["d"]["mute"].ToObject(); - memberToUpdate.Muted = e.Mute; - e.Suppress = message["d"]["suppress"].ToObject(); - memberToUpdate.Deaf = e.Suppress; - e.RawJson = message; - - e.User = memberToUpdate; - - if (VoiceClient != null && VoiceClient.Connected) - VoiceClient.MemberAdded(e.User); - - if (!message["d"]["session_id"].IsNullOrEmpty()) //then this has to do with you - { - if (e.User.ID == Me.ID) - { - Me.Muted = e.SelfMute; - Me.Deaf = e.SelfDeaf; - if (VoiceClient != null) - VoiceClient.SessionID = message["d"]["session_id"].ToString(); - } - } - - if (VoiceStateUpdate != null) - VoiceStateUpdate(this, e); - } - } - - private JObject ServerInfo(string channelOrServerId) - { - string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{channelOrServerId}"; - try - { - return JObject.Parse(WebWrapper.Get(url, token)); - } - catch (Exception ex) - { - DebugLogger.Log("(Private) Error ocurred while retrieving server info: " + ex.Message, MessageLevel.Error); - } - return null; - } - - private int HeartbeatInterval = 41250; - private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - private void KeepAlive() - { - //string keepAliveJson = "{\"op\":" + Opcodes.HEARTBEAT + ", \"d\":" + Sequence + "}"; - string keepAliveJson = JsonConvert.SerializeObject(new - { - op = Opcodes.HEARTBEAT, - d = Sequence - }); - if (ws != null) - { - ws.Send(keepAliveJson); - KeepAliveSent?.Invoke(this, new DiscordKeepAliveSentEventArgs { SentAt = DateTime.Now, JsonSent = keepAliveJson }); - } - } - - /// - /// Disposes. - /// - public void Dispose() - { - try - { - KeepAliveTaskTokenSource.Cancel(); - ws.Close(); - ws = null; - ServersList = null; - PrivateChannels = null; - Me = null; - token = null; - this.ClientPrivateInformation = null; - } - catch { /*already been disposed elsewhere */ } - } - - /// - /// Logs out of Discord and then disposes. - /// - public void Logout() - { - string url = Endpoints.BaseAPI + Endpoints.Auth + "/logout"; - string msg = JsonConvert.SerializeObject(new { token = token }); - WebWrapper.Post(url, msg); - Dispose(); - } - - /// - /// Sends a login request. - /// - /// The token if login was succesful, or null if not - public string SendLoginRequest() - { - if (token == null) //no token override provided, need to read token - { - if (String.IsNullOrEmpty(ClientPrivateInformation.Email)) - { - throw new ArgumentNullException("Email was null/invalid!"); - } - StrippedEmail = ClientPrivateInformation.Email.Replace('@', '_').Replace('.', '_'); //strips characters from email for hashing - - if (File.Exists(StrippedEmail.GetHashCode() + ".cache")) - { - string read = ""; - using (var sr = new StreamReader(StrippedEmail.GetHashCode() + ".cache")) - { - read = sr.ReadLine(); - if (read.StartsWith("#")) //comment - { - token = sr.ReadLine(); - DebugLogger.Log("Loading token from cache."); - } - token = token.Trim(); //trim any excess whitespace - } - } - else - { - if (ClientPrivateInformation == null || ClientPrivateInformation.Email == null || ClientPrivateInformation.Password == null) - throw new ArgumentNullException("You didn't supply login information!"); - string url = Endpoints.BaseAPI + Endpoints.Auth + Endpoints.Login; - string msg = JsonConvert.SerializeObject(new - { - email = ClientPrivateInformation.Email, - password = ClientPrivateInformation.Password - }); - DebugLogger.Log("No token present, sending login request.."); - var result = JObject.Parse(WebWrapper.Post(url, msg)); - - if (result["token"].IsNullOrEmpty()) - { - string message = "Failed to login to Discord."; - if (!result["email"].IsNullOrEmpty()) - message += " Email was invalid: " + result["email"]; - if (!result["password"].IsNullOrEmpty()) - message += " password was invalid: " + result["password"]; - - throw new DiscordLoginException(message); - } - token = result["token"].ToString(); - - using (var sw = new StreamWriter(StrippedEmail.GetHashCode() + ".cache")) - { - sw.WriteLine($"#Token cache for {ClientPrivateInformation.Email}"); - sw.WriteLine(token); - DebugLogger.Log($"{StrippedEmail.GetHashCode()}.cache written!"); - } - } - } - return token; - } - } -} \ No newline at end of file diff --git a/DiscordSharp/DiscordSharp.csproj b/DiscordSharp/DiscordSharp.csproj index 70d4ba4..78543d9 100644 --- a/DiscordSharp/DiscordSharp.csproj +++ b/DiscordSharp/DiscordSharp.csproj @@ -102,10 +102,14 @@ + + + + @@ -143,7 +147,6 @@ - diff --git a/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs b/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs index b950c69..23d58d0 100644 --- a/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs +++ b/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs @@ -1,15 +1,103 @@ using System; using DiscordSharp.Objects; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DiscordSharp { public class DiscordPrivateMessageEventArgs : EventArgs { + public DiscordMessageDirection MessageDirection { get; internal set; } public DiscordPrivateChannel Channel { get; internal set; } public DiscordMember Author { get; internal set; } public string Message { get; internal set; } public MessageAttachment[] Attachments { get; internal set; } + public BaseMessage BaseMessage { get; internal set; } public JObject RawJson { get; internal set; } } + + public enum DiscordMessageDirection + { + Default, + In, + Out, + } + + public class BaseMessage + { + public static BaseMessage TryParse(JToken input) + { + if (input == null) + return null; + try + { + return input.ToObject(); + } + catch (Exception e) + { + // Console.WriteLine(e); + } + return null; + } + + [JsonProperty("attachments")] + public object[] Attachments { get; set; } + + [JsonProperty("author")] + public MessageAuthor Author { get; set; } + + [JsonProperty("channel_id")] + public string ChannelId { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } + + [JsonProperty("edited_timestamp")] + public object EditedTimestamp { get; set; } + + [JsonProperty("embeds")] + public object[] Embeds { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("mention_everyone")] + public bool MentionEveryone { get; set; } + + [JsonProperty("mention_roles")] + public object[] MentionRoles { get; set; } + + [JsonProperty("mentions")] + public object[] Mentions { get; set; } + + [JsonProperty("nonce")] + public string Nonce { get; set; } + + [JsonProperty("pinned")] + public bool Pinned { get; set; } + + [JsonProperty("timestamp")] + public string Timestamp { get; set; } + + [JsonProperty("tts")] + public bool Tts { get; set; } + + [JsonProperty("type")] + public long Type { get; set; } + } + + public class MessageAuthor + { + [JsonProperty("avatar")] + public object Avatar { get; set; } + + [JsonProperty("discriminator")] + public string Discriminator { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("username")] + public string Username { get; set; } + } } \ No newline at end of file diff --git a/DiscordSharp/Objects/DiscordChannel.cs b/DiscordSharp/Objects/DiscordChannel.cs index e633f48..9f4a460 100644 --- a/DiscordSharp/Objects/DiscordChannel.cs +++ b/DiscordSharp/Objects/DiscordChannel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using DiscordSharp.Discord; namespace DiscordSharp.Objects { @@ -10,12 +11,15 @@ public abstract class DiscordChannelBase { [JsonProperty("id")] public string ID { get; internal set; } + [JsonProperty("is_private")] public bool Private { get; internal set; } internal DiscordClient Client { get; set; } - internal DiscordChannelBase() { } + internal DiscordChannelBase() + { + } /// /// Simulates typing in the specified channel. Automatically times out/stops after either: @@ -116,7 +120,9 @@ public DiscordChannel ShallowCopy() return channel; } - internal DiscordChannel() { } + internal DiscordChannel() + { + } } public class DiscordPrivateChannel : DiscordChannelBase @@ -124,10 +130,13 @@ public class DiscordPrivateChannel : DiscordChannelBase internal string user_id { get; set; } public DiscordMember Recipient { get; set; } + [JsonProperty("last_message_id")] private string LastMessageID { get; set; } - internal DiscordPrivateChannel() { } + internal DiscordPrivateChannel() + { + } } //kinda like the author @@ -136,8 +145,9 @@ public class DiscordRecipient { public string username { get; set; } public string id { get; set; } - internal DiscordRecipient() { } - } - -} + internal DiscordRecipient() + { + } + } +} \ No newline at end of file diff --git a/DiscordSharp/Objects/DiscordMember.cs b/DiscordSharp/Objects/DiscordMember.cs index 1d95621..50bcd34 100644 --- a/DiscordSharp/Objects/DiscordMember.cs +++ b/DiscordSharp/Objects/DiscordMember.cs @@ -8,6 +8,7 @@ using System.Net; using System.Text; using System.Threading.Tasks; +using DiscordSharp.Discord; namespace DiscordSharp.Objects { @@ -15,22 +16,30 @@ public enum Status { Online, Idle, Offline } + public class DiscordMember { [JsonProperty("username")] public string Username { get; internal set; } + [JsonProperty("id")] public string ID { get; internal set; } + [JsonProperty("discriminator")] public string Discriminator { get; internal set; } + [JsonProperty("avatar")] public string Avatar { get; internal set; } + [JsonProperty("verified")] public bool Verified { get; internal set; } + [JsonProperty("bot")] public bool IsBot { get; internal set; } = false; + [JsonProperty("joined_at")] public DateTime JoinedAt { get; internal set; } + [JsonProperty("nick")] public string Nickname { get; internal set; } = ""; @@ -42,10 +51,13 @@ public class DiscordMember /** Voice only */ + [JsonProperty("mute")] public bool Muted { get; internal set; } = false; + [JsonProperty("deaf")] public bool Deaf { get; internal set; } = false; + public DiscordChannel CurrentVoiceChannel { get; internal set; } public DiscordVoiceState VoiceState { get; internal set; } @@ -60,21 +72,20 @@ internal void SetPresence(string status) Status = Status.Offline; } - /// /// Applicable only for the currently signed in user. /// public string Email { get; internal set; } - public List Roles { get; set; } /// /// The server the user belongs to. /// public DiscordServer Parent { get; internal set; } + internal DiscordClient parentclient { get; set; } - + internal DiscordMember(DiscordClient parent) { Roles = new List(); @@ -99,7 +110,7 @@ public Bitmap GetAvatar() } /// - /// + /// /// /// The URL of the user's avatar. public Uri GetAvatarURL() @@ -108,12 +119,12 @@ public Uri GetAvatarURL() } /// - /// Kicks this DiscordMember from the guild that's assumed from their + /// Kicks this DiscordMember from the guild that's assumed from their /// parent property. /// public void Kick() { - if(parentclient.Me.ID == this.ID) + if (parentclient.Me.ID == this.ID) throw new InvalidOperationException("Can't kick self!"); string url = Endpoints.BaseAPI + Endpoints.Guilds + $"/{Parent.ID}" + Endpoints.Members + $"/{ID}"; try @@ -226,6 +237,7 @@ public void SendMessage(string message) parentclient.GetTextClientLogger.Log($"Error ocurred while sending message to user, step 1: {ex.Message}", MessageLevel.Error); } } + private void SendActualMessage(string id, string message) { string url = Endpoints.BaseAPI + Endpoints.Channels + $"/{id}" + Endpoints.Messages; @@ -246,7 +258,7 @@ private bool Equals(DiscordMember obj) return this.ID == obj.ID; } - public static bool operator==(DiscordMember x, DiscordMember y) + public static bool operator ==(DiscordMember x, DiscordMember y) { if ((object)x == null && (object)y == null) return true; @@ -268,10 +280,10 @@ private bool Equals(DiscordMember obj) public override bool Equals(object obj) { - if(obj.GetType() == typeof(DiscordMember)) + if (obj.GetType() == typeof(DiscordMember)) return this == (DiscordMember)obj; else return false; } } -} +} \ No newline at end of file diff --git a/DiscordSharp/Objects/DiscordMessageType.cs b/DiscordSharp/Objects/DiscordMessageType.cs new file mode 100644 index 0000000..f8ae107 --- /dev/null +++ b/DiscordSharp/Objects/DiscordMessageType.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DiscordSharp.Objects +{ + /// + /// The type of message that the Discord message is. + /// + public enum DiscordMessageType + { + /// + /// Private/DM + /// + PRIVATE, + + /// + /// Public/in a channel. + /// + CHANNEL + } +} \ No newline at end of file diff --git a/DiscordSharp/Objects/DiscordProperties.cs b/DiscordSharp/Objects/DiscordProperties.cs new file mode 100644 index 0000000..b81e897 --- /dev/null +++ b/DiscordSharp/Objects/DiscordProperties.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace DiscordSharp.Objects +{ + /// + /// Properties that Discord uses upon connection to the websocket. Mostly used for analytics internally. + /// + public class DiscordProperties + { + /// + /// The OS you're on + /// + [JsonProperty("$os")] + public string OS { get; set; } + + /// + /// The "browser" you're using. + /// + [JsonProperty("$browser")] + public string Browser { get; set; } + + /// + /// Whatever device you want to be on. (Default: DiscordSharp Bot) + /// + [JsonProperty("$device")] + public string Device + { get; set; } = "DiscordSharp Bot"; + + /// + /// + /// + [JsonProperty("$referrer")] + public string referrer { get; set; } + + /// + /// + /// + [JsonProperty("$referring_domain")] + public string referring_domain { get; set; } + + /// + /// Default constructor setting the OS property to Environment.OSVersion.ToString(); + /// + public DiscordProperties() + { + OS = Environment.OSVersion.ToString(); + } + + /// + /// Serializes this object as json. + /// + /// Json string of this object serialized + public string AsJson() + { + return JsonConvert.SerializeObject(this); + } + } +} \ No newline at end of file diff --git a/DiscordSharp/Objects/DiscordServer.cs b/DiscordSharp/Objects/DiscordServer.cs index 74f5116..c2d3a78 100644 --- a/DiscordSharp/Objects/DiscordServer.cs +++ b/DiscordSharp/Objects/DiscordServer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; +using DiscordSharp.Discord; using ID = System.String; namespace DiscordSharp.Objects @@ -15,6 +16,7 @@ public class DiscordServer [JsonProperty("id")] public ID ID { get; internal set; } + [JsonProperty("name")] public string Name { get; internal set; } @@ -29,6 +31,7 @@ public class DiscordServer [JsonProperty("icon")] internal string icon { get; set; } + public string IconURL { get @@ -41,6 +44,7 @@ public string IconURL #pragma warning disable 0612 private DiscordMember _owner; + [JsonProperty("owner")] public DiscordMember Owner { @@ -50,6 +54,7 @@ internal set _owner = value; } } + #pragma warning restore 0612 [JsonProperty("channels")] @@ -79,12 +84,11 @@ internal DiscordServer() Members = new Dictionary(); } - internal void AddMember(DiscordMember member) { if (member == null) return; - if(Members.ContainsKey(member.ID)) //then replace + if (Members.ContainsKey(member.ID)) //then replace { Members.Remove(member.ID); } @@ -94,21 +98,23 @@ internal void AddMember(DiscordMember member) internal int ClearOfflineMembers() { int count = 0; - foreach(var member in Members) + foreach (var member in Members) { if (member.Value.Status == Status.Offline) return count; } return count; } + internal bool RemoveMember(ID key) { - if(Members.ContainsKey(key)) + if (Members.ContainsKey(key)) { Members.Remove(key); } return false; } + public DiscordMember GetMemberByKey(ID key) { if (Unavailable) @@ -123,6 +129,7 @@ public DiscordMember GetMemberByKey(ID key) return null; //because instead of just returning null by default, it has to do this shit. } } + public DiscordMember GetMemberByUsername(string username, bool caseSensitive = false) { if (Unavailable) @@ -168,6 +175,7 @@ public void AssignRoleToMember(DiscordRole role, DiscordMember member) string message = JsonConvert.SerializeObject(new { roles = new string[] { role.ID } }); Console.WriteLine(WebWrapper.Patch(url, DiscordClient.token, message)); } + public void AssignRoleToMember(List roles, DiscordMember member) { if (Unavailable) @@ -273,7 +281,8 @@ public DiscordChannel CreateChannel(string ChannelName, bool voice) var result = JObject.Parse(WebWrapper.Post(url, DiscordClient.token, reqJson)); if (result != null) { - DiscordChannel dc = new DiscordChannel { + DiscordChannel dc = new DiscordChannel + { Name = result["name"].ToString(), ID = result["id"].ToString(), Type = result["type"].ToObject(), @@ -347,6 +356,7 @@ public void RemoveBan(string userID) parentclient.GetTextClientLogger.Log($"Error during RemoveBan\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Error); } } + public void RemoveBan(DiscordMember member) { if (Unavailable) @@ -368,4 +378,4 @@ public DiscordServer ShallowCopy() return (DiscordServer)this.MemberwiseClone(); } } -} +} \ No newline at end of file diff --git a/DiscordSharp/Voice/DiscordVoiceClient.cs b/DiscordSharp/Voice/DiscordVoiceClient.cs index 7b0544b..1205296 100644 --- a/DiscordSharp/Voice/DiscordVoiceClient.cs +++ b/DiscordSharp/Voice/DiscordVoiceClient.cs @@ -1,4 +1,5 @@ extern alias ws4n; + using DiscordSharp.Events; using DiscordSharp.Objects; using DiscordSharp.Voice; @@ -13,9 +14,13 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using DiscordSharp.Discord; + #if NETFX4_5 + using ws4n.WebSocket4Net; using System.Collections.Concurrent; + #else using ws4n.WebSocket4Net; using CONCURRENT = System.Collections.Concurrent; @@ -39,7 +44,7 @@ 2a. The json you receive will contain some IMPORTANT information including voice 4. Point your websocket client to the given endpoint and upon connection, send opcode 0 with the server ID, channel ID, your client ID, and that token you got prior. 5. Opcode 2 gives you various parameters for your voice connection. 5a. ssrc is basically your voice client ID. You will need it for your packet. It differentiates between users. - 5b. Guess what port is for. + 5b. Guess what port is for. 5c. (I believe) modes is for voice encryption. 5d. heartbeat_interval is very important. The voice connection is extremely finnicky with its keepalives so **DONT HARDCODE THIS VALUE**. It's usually 5500 ms. 5e. After opcode 2, you can begin your websocket keepalive. @@ -60,14 +65,17 @@ internal class VoiceConnectionParameters { [JsonProperty("ssrc")] public int ssrc { get; internal set; } + [JsonProperty("port")] public int port { get; internal set; } + [JsonProperty("modes")] public string[] modes { get; internal set; } + [JsonProperty("heartbeat_interval")] public int heartbeat_interval { get; internal set; } } - + internal struct DiscordIpPort { public IPAddress Address; @@ -78,6 +86,7 @@ public class DiscordAudioPacketEventArgs : EventArgs { //public DiscordAudioPacket Packet { get; internal set; } public byte[] OpusAudio { get; internal set; } + public int OpusAudioLength { get; internal set; } public DiscordChannel Channel { get; internal set; } public DiscordMember FromUser { get; internal set; } @@ -86,6 +95,7 @@ public class DiscordAudioPacketEventArgs : EventArgs public class DiscordVoiceConfig { internal int SampleRate { get; set; } = 48000; //discord default + /// /// The amount of channels you wish to send over the network. /// Use 2 for stereo. Stereo will require more bandwidth. @@ -126,7 +136,7 @@ public int PCMBlockSize } } } - + public class DiscordVoiceClient : IDisposable { private DiscordClient _parent; @@ -163,20 +173,29 @@ public class DiscordVoiceClient : IDisposable public DiscordVoiceConfig VoiceConfig { get; internal set; } #region Events + internal event EventHandler DebugMessageReceived; + internal event EventHandler Disposed; + internal event EventHandler UserSpeaking; + internal event EventHandler PacketReceived; + internal event EventHandler ErrorReceived; + internal event EventHandler VoiceConnectionComplete; + internal event EventHandler QueueEmpty; -#endregion -#region voice sending stuff + #endregion Events + + #region voice sending stuff + /// /// The length, in ms, of audio for DiscordSharp to send. By default, 20. /// - static int msToSend = 20; + private static int msToSend = 20; /// /// An signed short designating the sequence of the audio being sent. @@ -187,7 +206,8 @@ public class DiscordVoiceClient : IDisposable /// Unsigned int designating the timestamp offset of the audio being sent. /// internal uint ___timestamp = 0; -#endregion + + #endregion voice sending stuff public Logger GetDebugLogger => VoiceDebugLogger; @@ -237,13 +257,13 @@ internal void InitializeOpusEncoder() mainOpusEncoder.SetForwardErrorCorrection(true); msToSend = VoiceConfig.FrameLengthMs; } - if(!VoiceConfig.SendOnly) + if (!VoiceConfig.SendOnly) InitializeOpusDecoder(); } private void InitializeOpusDecoder() { - if(Channel != null && Channel.Type == ChannelType.Voice) + if (Channel != null && Channel.Type == ChannelType.Voice) { Decoder = new OpusDecoder(VoiceConfig.SampleRate, VoiceConfig.Channels, VoiceConfig.FrameLengthMs); } @@ -254,7 +274,7 @@ private void InitializeOpusDecoder() /// public void Initiate() { - if(Me == null) + if (Me == null) { if (_parent != null) { @@ -286,7 +306,7 @@ public void Initiate() { VoiceWebSocket_OnMessage(s, e); } - catch(Exception ex) + catch (Exception ex) { VoiceDebugLogger.Log($"Exception while awaiting OnMessage?!\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Critical); } @@ -312,8 +332,9 @@ public void Initiate() VoiceWebSocket.Open(); } - + #pragma warning disable 4014 //stupid await warnings + private void VoiceWebSocket_OnMessage(object sender, MessageReceivedEventArgs e) { JObject message = JObject.Parse(e.Message); @@ -325,14 +346,16 @@ private void VoiceWebSocket_OnMessage(object sender, MessageReceivedEventArgs e) //ok, now that we have opcode 2 we have to send a packet and configure the UDP InitialUDPConnection(); break; + case 3: VoiceDebugLogger.Log("KeepAlive echoed back successfully!", MessageLevel.Unecessary); break; + case 4: //you get your secret key from here //post initializing the UDP client, we will receive opcode 4 and will now do the final connection steps OpCode4(message); //if (!VoiceConfig.SendOnly) - DoUDPKeepAlive(globalTaskSource.Token); + DoUDPKeepAlive(globalTaskSource.Token); SendVoiceTask(globalTaskSource.Token); if (!VoiceConfig.SendOnly) ReceiveVoiceTask(globalTaskSource.Token); @@ -340,15 +363,18 @@ private void VoiceWebSocket_OnMessage(object sender, MessageReceivedEventArgs e) if (VoiceConnectionComplete != null) VoiceConnectionComplete(this, new EventArgs()); break; + case 5: //User speaking //VoiceDebugLogger.Log(e.Message); OpCode5(message); break; } } + #pragma warning restore 4014 //stupid await warnings -#region Websocket Opcode Events/other misc events + #region Websocket Opcode Events/other misc events + internal void MemberRemoved(DiscordMember removed) { if (MembersInChannel.Contains(removed)) @@ -357,11 +383,12 @@ internal void MemberRemoved(DiscordMember removed) VoiceDebugLogger.Log($"User {removed.Username} ({removed.ID}) left the client's current connected voice channel."); } } + internal void MemberAdded(DiscordMember added) { - if(added.Parent == Guild) + if (added.Parent == Guild) { - if(added.CurrentVoiceChannel == Channel) + if (added.CurrentVoiceChannel == Channel) { if (MembersInChannel.Contains(added)) MembersInChannel.Remove(added); @@ -370,6 +397,7 @@ internal void MemberAdded(DiscordMember added) } } } + private void OpCode5(JObject message) { DiscordVoiceUserSpeakingEventArgs e = new DiscordVoiceUserSpeakingEventArgs(); @@ -378,7 +406,7 @@ private void OpCode5(JObject message) e.Speaking = message["d"]["speaking"].ToObject(); e.Ssrc = message["d"]["ssrc"].ToObject(); - if(e.UserSpeaking != null) + if (e.UserSpeaking != null) { if (!SsrcDictionary.ContainsKey(e.UserSpeaking)) SsrcDictionary.Add(e.UserSpeaking, e.Ssrc); @@ -409,6 +437,7 @@ private void OpCode4(JObject message) } #pragma warning disable 4014 + private void OpCode2(JObject message) { Params = JsonConvert.DeserializeObject(message["d"].ToString()); @@ -424,6 +453,7 @@ private void OpCode2(JObject message) //SendWebSocketKeepalive(); DoWebSocketKeepAlive(globalTaskSource.Token); } + #pragma warning restore 4014 private void VoiceWebSocket_OnError(object sender, EventArgs e) @@ -438,34 +468,38 @@ private void VoiceWebSocket_OnClose(object sender, EventArgs e) { VoiceDebugLogger.Log($"VoiceWebSocket was closed.", MessageLevel.Critical); ErrorReceived?.Invoke(this, new EventArgs()); - + Dispose(); } -#endregion + + #endregion Websocket Opcode Events/other misc events private bool QueueEmptyEventTriggered = false; -#region Internal Voice Methods + + #region Internal Voice Methods + #pragma warning disable 4014 + private void SendVoiceTask(CancellationToken token) { - VoiceSendThread = new Thread(() => - { - while (!token.IsCancellationRequested) - { - if (!voiceToSend.IsEmpty) - { - QueueEmptyEventTriggered = false; - try - { - SendVoiceAsync(token); - } - catch (Exception ex) - { - VoiceDebugLogger.Log(ex.Message, MessageLevel.Error); - } - } - else - { + VoiceSendThread = new Thread(() => + { + while (!token.IsCancellationRequested) + { + if (!voiceToSend.IsEmpty) + { + QueueEmptyEventTriggered = false; + try + { + SendVoiceAsync(token); + } + catch (Exception ex) + { + VoiceDebugLogger.Log(ex.Message, MessageLevel.Error); + } + } + else + { #if NETFX4_5 //await Task.Delay(1000).ConfigureAwait(false); Thread.Sleep(1000); @@ -473,25 +507,27 @@ private void SendVoiceTask(CancellationToken token) Thread.Sleep(1000); #endif } - if (___sequence > 0 || ___timestamp > 0) - { - if (voiceToSend.IsEmpty) - { + if (___sequence > 0 || ___timestamp > 0) + { + if (voiceToSend.IsEmpty) + { //reset these ___sequence = 0; - ___timestamp = 0; - if (!QueueEmptyEventTriggered) - { - QueueEmpty?.Invoke(this, new EventArgs()); - QueueEmptyEventTriggered = true; - } - } - } - } - }); + ___timestamp = 0; + if (!QueueEmptyEventTriggered) + { + QueueEmpty?.Invoke(this, new EventArgs()); + QueueEmptyEventTriggered = true; + } + } + } + } + }); VoiceSendThread.Start(); } + #pragma warning disable 4014 + private Task ReceiveVoiceTask(CancellationToken token) { VoiceDebugLogger.Log("Setting up for voice receive."); @@ -501,7 +537,7 @@ private Task ReceiveVoiceTask(CancellationToken token) return Task.Factory.StartNew(async () => #endif { - while(!token.IsCancellationRequested) + while (!token.IsCancellationRequested) { if (_udp.Available > 0) { @@ -530,7 +566,7 @@ private async Task DoReceiveVoice() int packetLength, resultOffset, resultLength; decodingBuffer = new byte[4000]; nonce = new byte[24]; - packetLength = packet.Length; + packetLength = packet.Length; if (packet.Length > 0) { @@ -566,7 +602,6 @@ private async Task DoReceiveVoice() }); } } - } } } @@ -581,6 +616,7 @@ private DiscordMember GetUserBySsrc(uint ssrc) } #pragma warning restore 4014 + private void SendVoiceAsync(CancellationToken cancelToken) { byte[] opusAudio; //pcm data @@ -603,7 +639,7 @@ private void SendVoiceAsync(CancellationToken cancelToken) //byte[] opusAudio = new byte[queuedOpus.Length - 4]; //Buffer.BlockCopy(queuedOpus, 0, opusAudio, 0, opusAudio.Length); //int encodedLength = mainOpusEncoder.EncodeFrame(voiceToEncode, 0, opusAudio); - + int encodedLength = BitConverter.ToInt32(opusAudio, opusAudio.Length - 4); int dataSent = 0; @@ -667,7 +703,7 @@ private void SendVoiceAsync(CancellationToken cancelToken) private void DoWebSocketKeepAlive(CancellationToken token) { - WebsocketKeepAliveThread = new Thread(() => + WebsocketKeepAliveThread = new Thread(() => { try { @@ -696,7 +732,6 @@ private void DoWebSocketKeepAlive(CancellationToken token) private void DoUDPKeepAlive(CancellationToken token) { - UDPKeepAliveThread = new Thread(() => { byte[] keepAlive = new byte[5]; @@ -728,6 +763,7 @@ private void DoUDPKeepAlive(CancellationToken token) }); UDPKeepAliveThread.Start(); } + private async Task InitialUDPConnection() { try @@ -741,7 +777,6 @@ private async Task InitialUDPConnection() udpEndpoint = new IPEndPoint(Dns.GetHostAddresses(VoiceEndpoint.Replace(":80", ""))[0], 80); - byte[] packet = new byte[70]; //the initial packet packet[0] = (byte)((Params.ssrc >> 24) & 0xFF); packet[1] = (byte)((Params.ssrc >> 16) & 0xFF); @@ -784,6 +819,7 @@ private async Task InitialUDPConnection() VoiceDebugLogger.Log("UDP Client Error: " + ex.Message, MessageLevel.Critical); } } + /// /// Sends our IP over UDP for Discord's voice server to process. Also sends op 1 /// @@ -838,9 +874,11 @@ private DiscordIpPort GetIPAndPortFromPacket(byte[] packet) VoiceDebugLogger.Log($"Our IP is {returnVal.Address} and we're using port {returnVal.port}."); return returnVal; } -#endregion -#region Public Methods + #endregion Internal Voice Methods + + #region Public Methods + /// /// Enqueues audio to be sent through the UDP client. /// @@ -867,7 +905,7 @@ public void ClearVoiceQueue() byte[] ignored; while (voiceToSend.TryDequeue(out ignored)) ; } - + /// /// Echos a given DiscordAudioPacket. /// @@ -877,6 +915,7 @@ public async Task EchoPacket(DiscordAudioPacket packet) { await SendPacket(DiscordAudioPacket.EchoPacket(packet.AsRawPacket(), Params.ssrc)).ConfigureAwait(false); } + /// /// Sends a given DiscordAudioPacket over the UDP client.. /// @@ -894,6 +933,7 @@ public async Task SendPacket(DiscordAudioPacket packet) VoiceDebugLogger.Log("Sent packet through SendPacket task.", MessageLevel.Unecessary); } } + /// /// Whether or not the current client is set to be speaking. /// @@ -922,16 +962,19 @@ public void SetSpeaking(bool speaking) else VoiceDebugLogger.Log("VoiceWebSocket null?", MessageLevel.Critical); } -#endregion -#region Cleanup - bool disposed = false; - SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true); + #endregion Public Methods + + #region Cleanup + + private bool disposed = false; + private SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true); + protected virtual void Dispose(bool disposing) { if (disposed) return; - if(disposing) + if (disposing) { handle.Dispose(); @@ -959,11 +1002,12 @@ protected virtual void Dispose(bool disposing) public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); - + GC.SuppressFinalize(this); + if (Disposed != null) Disposed(this, new EventArgs()); } -#endregion + + #endregion Cleanup } -} +} \ No newline at end of file diff --git a/DiscordSharp/WebWrapper.cs b/DiscordSharp/WebWrapper.cs index cdfa24e..6b6ff6b 100644 --- a/DiscordSharp/WebWrapper.cs +++ b/DiscordSharp/WebWrapper.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using DiscordSharp.Discord; namespace DiscordSharp { @@ -17,7 +18,7 @@ namespace DiscordSharp /// internal class WebWrapper { - static string UserAgentString = $"DiscordBot (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version.ToString()})"; + private static string UserAgentString = $"DiscordBot (http://github.com/Luigifan/DiscordSharp, {typeof(DiscordClient).Assembly.GetName().Version.ToString()})"; /// /// Sends a DELETE HTTP request to the specified URL using the specified token. @@ -141,7 +142,7 @@ public static string Post(string url, string token, string message, bool acceptI try { var httpResponse = (HttpWebResponse)httpRequest.GetResponse(); - if(httpResponse.StatusCode == HttpStatusCode.Unauthorized) + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) { throw new UnauthorizedAccessException("401"); } @@ -325,6 +326,7 @@ public static string Post(string url, string message) } return ""; } + [Obsolete] public static string PostWithAttachment(string url, string message, string fileToAttach) { @@ -456,7 +458,7 @@ public static string Get(string url, string token) try { var httpResponse = (HttpWebResponse)httpRequest.GetResponse(); - if(httpResponse.StatusCode == HttpStatusCode.Unauthorized) + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) { throw new UnauthorizedAccessException("401"); } @@ -476,11 +478,10 @@ public static string Get(string url, string token) } } } - catch(Exception ) //must be a jarray + catch (Exception) //must be a jarray { return result; } - } return result; } @@ -494,6 +495,5 @@ public static string Get(string url, string token) } } } - } } \ No newline at end of file diff --git a/DiscordSharpTestApplication/LuigibotMain.cs b/DiscordSharpTestApplication/LuigibotMain.cs index 647c922..0aa3033 100644 --- a/DiscordSharpTestApplication/LuigibotMain.cs +++ b/DiscordSharpTestApplication/LuigibotMain.cs @@ -17,6 +17,7 @@ using YugiohPrices; using Luigibot.Modules; using System.Linq; +using DiscordSharp.Discord; namespace Luigibot { @@ -49,28 +50,34 @@ public class LuigibotMain private CommandsManager CommandsManager; private CancellationToken cancelToken; private DateTime loginDate = DateTime.Now; + #region Audio playback + WaveFormat waveFormat; BufferedWaveProvider bufferedWaveProvider; WaveCallbackInfo waveCallbackInfo; IWavePlayer outputDevice; VolumeWaveProvider16 volumeProvider; System.Timers.Timer stutterReducingTimer; - #endregion + + #endregion Audio playback + private bool runningOnMono = false; private const int voiceMsBuffer = 20; private string osString; public bool actuallyExit = false; - + string[] NaughtyWords = new string[] { "bitch", "fucking", "fuck", "cunt", "shit", "reest", "reested", "asswipe" }; #region Initial Run + bool doingInitialRun = false; string codeToEnter = ""; -#endregion + + #endregion Initial Run public LuigibotMain() { @@ -86,9 +93,9 @@ public LuigibotMain() if (config.CommandPrefix.ToString().Length == 0) config.CommandPrefix = '?'; - runningOnMono = Type.GetType ("Mono.Runtime") != null; - - osString = OSDetermination.GetUnixName(); + runningOnMono = Type.GetType("Mono.Runtime") != null; + + osString = OSDetermination.GetUnixName(); } public void RunLuigibot() @@ -98,7 +105,7 @@ public void RunLuigibot() Console.Title = "Luigibot - Discord"; DoLogin(); } - + private void WriteError(string text) { Console.ForegroundColor = ConsoleColor.Red; @@ -165,7 +172,6 @@ private Task SetupEvents(CancellationToken token) } } - if (e.Author == null) { string msg = $"Author had null id in message received!\nRaw JSON:\n```\n{e.RawJson}\n```\n"; @@ -211,7 +217,7 @@ private Task SetupEvents(CancellationToken token) { e.Channel.SendMessage(ex.Message); } - catch(ModuleNotEnabledException x) + catch (ModuleNotEnabledException x) { e.Channel.SendMessage($"{x.Message}"); } @@ -243,13 +249,13 @@ private Task SetupEvents(CancellationToken token) // } //} - if(e.Channel.ID == "91265608326324224") //discord-sharp on discordapi + if (e.Channel.ID == "91265608326324224") //discord-sharp on discordapi { - if(e.Author != owner) + if (e.Author != owner) { - if(e.Message.Content != null && e.Message.Content.ToLower().Contains("how")) + if (e.Message.Content != null && e.Message.Content.ToLower().Contains("how")) { - if(e.Message.Content.ToLower().Contains("bot") && e.Message.Content.ToLower().Contains("tag")) + if (e.Message.Content.ToLower().Contains("bot") && e.Message.Content.ToLower().Contains("tag")) { e.Channel.SendMessage($"<#124294271900712960>");//#api-changes } @@ -260,7 +266,7 @@ private Task SetupEvents(CancellationToken token) }; client.VoiceClientDebugMessageReceived += (sender, e) => { - if(e.message.Level != MessageLevel.Unecessary) + if (e.message.Level != MessageLevel.Unecessary) Console.WriteLine($"[{e.message.Level} {e.message.TimeStamp.ToString()}] {e.message.Message}"); }; client.VoiceClientConnected += (sender, e) => @@ -283,7 +289,7 @@ private Task SetupEvents(CancellationToken token) }; client.AudioPacketReceived += (sender, e) => { - if(bufferedWaveProvider != null) + if (bufferedWaveProvider != null) { byte[] potential = new byte[4000]; int decodedFrames = client.GetVoiceClient().Decoder.DecodeFrame(e.OpusAudio, 0, e.OpusAudioLength, potential); @@ -292,7 +298,7 @@ private Task SetupEvents(CancellationToken token) }; client.GuildCreated += (sender, e) => { - if(owner == null) + if (owner == null) owner = client.GetServersList().Find(x => x.GetMemberByKey(config.OwnerID) != null).GetMemberByKey(config.OwnerID); Console.WriteLine($"Joined server {e.Server.Name} ({e.Server.ID})"); try @@ -308,12 +314,12 @@ private Task SetupEvents(CancellationToken token) client.SocketClosed += (sender, e) => { Console.Title = "Luigibot - Discord - Socket Closed.."; - if(!actuallyExit) - { + if (!actuallyExit) + { WriteError($"\n\nSocket Closed Unexpectedly! Code: {e.Code}. Reason: {e.Reason}. Clear: {e.WasClean}.\n\n"); Console.WriteLine("Waiting 6 seconds to reconnect.."); Thread.Sleep(6 * 1000); - LetsGoAgain(); + LetsGoAgain(); } else { @@ -328,7 +334,7 @@ private Task SetupEvents(CancellationToken token) message += $"```\n{e.RawJson.ToString()}\n```\nIt's been dumped to `dumps/{e.RawJson["t"].ToString()}.json` for your viewing pleasure."; string filename = $"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}dumps{Path.DirectorySeparatorChar}{e.RawJson["t"].ToString()}.json"; - if(!File.Exists(filename)) + if (!File.Exists(filename)) { File.WriteAllText(e.RawJson.ToString(), filename); try @@ -352,12 +358,12 @@ private Task SetupEvents(CancellationToken token) if (e.message.Level == MessageLevel.Warning) WriteWarning($"(Logger Warning) {e.message.Message}"); }; - client.Connected += (sender, e) => + client.Connected += (sender, e) => { Console.Title = "Luigibot - Discord - Logged in as " + e.User.Username; Console.WriteLine("Connected as " + e.User.Username); if (!String.IsNullOrEmpty(config.OwnerID)) - { } + { } else { doingInitialRun = true; @@ -376,21 +382,21 @@ private Task SetupEvents(CancellationToken token) var permissionsDictionary = JsonConvert.DeserializeObject>(File.ReadAllText("permissions.json")); if (permissionsDictionary == null) permissionsDictionary = new Dictionary(); - if(permissionsDictionary.Count == 0 && owner != null) - permissionsDictionary.Add(owner.ID, PermissionType.Owner); - + if (permissionsDictionary.Count == 0 && owner != null) + permissionsDictionary.Add(owner.ID, PermissionType.Owner); + CommandsManager.OverridePermissionsDictionary(permissionsDictionary); } SetupCommands(); - if(config.ModulesDictionary != null) + if (config.ModulesDictionary != null) { CommandsManager.OverrideModulesDictionary(config.ModulesDictionary); } //client.UpdateCurrentGame($"DiscordSharp {typeof(DiscordClient).Assembly.GetName().Version.ToString()}"); }; - if(client.SendLoginRequest() != null) + if (client.SendLoginRequest() != null) { client.Connect(UseBuiltInWebsocket); } @@ -398,33 +404,33 @@ private Task SetupEvents(CancellationToken token) } //yes, this is a bullet for my valentine reference - private void LetsGoAgain() - { - client.Dispose (); - client = null; + private void LetsGoAgain() + { + client.Dispose(); + client = null; + + string botToken = File.ReadAllText("bot_token_important.txt"); + client = new DiscordClient(botToken, true); + client.RequestAllUsersOnStartup = true; - string botToken = File.ReadAllText("bot_token_important.txt"); - client = new DiscordClient(botToken, true); - client.RequestAllUsersOnStartup = true; - - client.ClientPrivateInformation.Email = config.BotEmail; - client.ClientPrivateInformation.Password = config.BotPass; + client.ClientPrivateInformation.Email = config.BotEmail; + client.ClientPrivateInformation.Password = config.BotPass; - SetupEvents(cancelToken); - } + SetupEvents(cancelToken); + } private void StutterReducingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { - if(outputDevice.PlaybackState != PlaybackState.Stopped) + if (outputDevice.PlaybackState != PlaybackState.Stopped) { - if(bufferedWaveProvider != null) + if (bufferedWaveProvider != null) { var bufferedSeconds = bufferedWaveProvider.BufferedDuration.TotalSeconds; - if(bufferedSeconds < 0.5 && outputDevice.PlaybackState == PlaybackState.Playing) + if (bufferedSeconds < 0.5 && outputDevice.PlaybackState == PlaybackState.Playing) { outputDevice.Pause(); } - else if(bufferedSeconds > 4 && outputDevice.PlaybackState == PlaybackState.Paused) + else if (bufferedSeconds > 4 && outputDevice.PlaybackState == PlaybackState.Paused) { outputDevice.Play(); } @@ -436,8 +442,8 @@ public void Exit() { config.ModulesDictionary = CommandsManager.ModuleDictionaryForJson(); File.WriteAllText("settings.json", JsonConvert.SerializeObject(config)); - if(CommandsManager.UserRoles != null && CommandsManager.UserRoles.Count > 0) - File.WriteAllText("permissions.json", JsonConvert.SerializeObject(CommandsManager.UserRoles)); + if (CommandsManager.UserRoles != null && CommandsManager.UserRoles.Count > 0) + File.WriteAllText("permissions.json", JsonConvert.SerializeObject(CommandsManager.UserRoles)); client.Logout(); client.Dispose(); @@ -448,6 +454,7 @@ public void Exit() private void SetupCommands() { #region Old Commands + CommandsManager.AddCommand(new CommandStub("invite", "Makes an invite to specified server given its ID", "Pass ID douchebag.", PermissionType.Owner, 1, cmdArgs => { if (cmdArgs.Args.Count > 0) @@ -466,11 +473,11 @@ private void SetupCommands() cmdArgs.Channel.SendMessage("kek"); } })); - - CommandsManager.AddCommand(new CommandStub("statusof", "`Status` test", "", PermissionType.Owner, 1, cmdArgs=> + + CommandsManager.AddCommand(new CommandStub("statusof", "`Status` test", "", PermissionType.Owner, 1, cmdArgs => { string id = cmdArgs.Args[0].Trim(new char[] { '<', '@', '>' }); - if(!string.IsNullOrEmpty(id)) + if (!string.IsNullOrEmpty(id)) { DiscordMember member = cmdArgs.Channel.Parent.GetMemberByKey(id); if (member != null) @@ -506,7 +513,7 @@ private void SetupCommands() })); CommandsManager.AddCommand(new CommandStub("serverstats", "Server stats", "help me", PermissionType.Owner, cmdArgs => { - if(cmdArgs.Channel != null && cmdArgs.Channel.Parent != null) + if (cmdArgs.Channel != null && cmdArgs.Channel.Parent != null) { DiscordServer guild = cmdArgs.Channel.Parent; string msg = $"Stats for **{guild.Name}**\n```\n"; @@ -519,11 +526,11 @@ private void SetupCommands() })); CommandsManager.AddCommand(new CommandStub("listroles", "Lists rolls", "help me", PermissionType.Owner, cmdArgs => { - if(cmdArgs.Channel != null && cmdArgs.Channel.Parent != null) + if (cmdArgs.Channel != null && cmdArgs.Channel.Parent != null) { DiscordServer guild = cmdArgs.Channel.Parent; string msg = $"Roles for **{guild.Name}**, per your request.\n```\n"; - foreach(var role in guild.Roles) + foreach (var role in guild.Roles) { msg += $"{role.Position} - {role.Name} - {role.ID} - {role.Permissions.GetRawPermissions()}\n"; } @@ -601,11 +608,11 @@ private void SetupCommands() })); CommandsManager.AddCommand(new CommandStub("moduleinfo", "Shows information about a specific module.", "", PermissionType.User, 1, cmdArgs => { - if(cmdArgs.Args.Count > 0 && cmdArgs.Args[0].Length > 0) + if (cmdArgs.Args.Count > 0 && cmdArgs.Args[0].Length > 0) { - foreach(var module in CommandsManager.Modules.ToList()) + foreach (var module in CommandsManager.Modules.ToList()) { - if(module.Key.Name.ToLower().Trim() == cmdArgs.Args[0].ToLower().Trim()) + if (module.Key.Name.ToLower().Trim() == cmdArgs.Args[0].ToLower().Trim()) { string msg = $"**About Module {module.Key.Name}**"; @@ -619,7 +626,8 @@ private void SetupCommands() } } })); - #endregion + + #endregion Old Commands var OwnerModules = new BaseOwnerModules(this); OwnerModules.Install(CommandsManager); @@ -718,10 +726,10 @@ private void TestRheaStream() } } } - catch(Exception ex) + catch (Exception ex) { owner.SendMessage("Exception during voice: `" + ex.Message + "`\n\n```" + ex.StackTrace + "\n```"); } } } -} +} \ No newline at end of file diff --git a/DiscordSharpTestApplication/Modules/EvalModules.cs b/DiscordSharpTestApplication/Modules/EvalModules.cs index 8ea417c..a3dccf7 100644 --- a/DiscordSharpTestApplication/Modules/EvalModules.cs +++ b/DiscordSharpTestApplication/Modules/EvalModules.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using DiscordSharp.Discord; namespace Luigibot.Modules { @@ -108,7 +109,6 @@ public override void Install(CommandsManager manager) else e.Channel.SendMessage("Errors!"); } - }); evalTask.Start(); evalTask.Wait(10 * 1000); @@ -186,4 +186,4 @@ public override void Install(CommandsManager manager) }), this); } } -} +} \ No newline at end of file diff --git a/DiscordSharpTestApplication/Modules/TestServerLog.cs b/DiscordSharpTestApplication/Modules/TestServerLog.cs index 1919a38..be36fd8 100644 --- a/DiscordSharpTestApplication/Modules/TestServerLog.cs +++ b/DiscordSharpTestApplication/Modules/TestServerLog.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using DiscordSharp.Discord; namespace Luigibot.Modules { @@ -22,7 +23,7 @@ public TestServerLog(DiscordClient client) DiscordSharpTestServer = client.GetServersList().Find( x => x.Name == "DiscordSharp Test Server" ); //todo replace with id - if(DiscordSharpTestServer != null) + if (DiscordSharpTestServer != null) { LogChannel = DiscordSharpTestServer.Channels.Find(x => x.Name == "log" && x.Type == ChannelType.Text); } @@ -36,7 +37,7 @@ cool hack manager.Client.GuildUpdated += (sender, e) => { Console.WriteLine($"[TestServerLog Module] Guild update. Old guild name: {e.OldServer.Name}"); - if(DiscordSharpTestServer != null) + if (DiscordSharpTestServer != null) { if (e.NewServer.ID == DiscordSharpTestServer.ID) //test server { @@ -83,4 +84,4 @@ cool hack }; } } -} +} \ No newline at end of file diff --git a/DiscordSharpTestApplication/Modules/Voice.cs b/DiscordSharpTestApplication/Modules/Voice.cs index 77d0c9d..84e4228 100644 --- a/DiscordSharpTestApplication/Modules/Voice.cs +++ b/DiscordSharpTestApplication/Modules/Voice.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using DiscordSharp.Discord; using VideoLibrary; namespace Luigibot.Modules @@ -39,7 +40,7 @@ private void SendVoice(string file, DiscordClient client, YouTubeVideo video) vc.SetSpeaking(true); - if(video.AudioFormat == AudioFormat.Mp3) + if (video.AudioFormat == AudioFormat.Mp3) { using (var mp3Reader = new Mp3FileReader(video.Stream())) { @@ -64,7 +65,7 @@ private void SendVoice(string file, DiscordClient client, YouTubeVideo video) } } } - else if(video.AudioFormat == AudioFormat.Vorbis) + else if (video.AudioFormat == AudioFormat.Vorbis) { using (var vorbis = new NAudio.Vorbis.VorbisWaveReader(video.Stream())) { @@ -237,4 +238,4 @@ public override void Install(CommandsManager manager) }), this); } } -} +} \ No newline at end of file diff --git a/DiscordSharpTestApplication/Program.cs b/DiscordSharpTestApplication/Program.cs index fd5d1fa..f24df9f 100644 --- a/DiscordSharpTestApplication/Program.cs +++ b/DiscordSharpTestApplication/Program.cs @@ -16,6 +16,7 @@ using NAudio.Wave.SampleProviders; using System.ComponentModel; using System.Diagnostics; +using DiscordSharp.Discord; using Luigibot; namespace DiscordSharpTestApplication @@ -100,13 +101,16 @@ static void WriteDebug(LogMessage m, string prefix) case MessageLevel.Debug: Console.ForegroundColor = ConsoleColor.Green; break; + case MessageLevel.Error: Console.ForegroundColor = ConsoleColor.Red; break; + case MessageLevel.Critical: Console.ForegroundColor = ConsoleColor.White; Console.BackgroundColor = ConsoleColor.Red; break; + case MessageLevel.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break; @@ -119,12 +123,12 @@ static void WriteDebug(LogMessage m, string prefix) [STAThread] public static void Main(string[] args) - { + { LuigibotMain luigibot = new LuigibotMain(); luigibot.RunLuigibot(); string output = ""; - while((output = Console.ReadLine()) != null) + while ((output = Console.ReadLine()) != null) { if (output == "") { @@ -133,6 +137,5 @@ public static void Main(string[] args) } } } - } } \ No newline at end of file