diff --git a/DiscordSharp.Commands/CommandStub.cs b/.NET core/DiscordSharp.Commands/CommandStub.cs
similarity index 100%
rename from DiscordSharp.Commands/CommandStub.cs
rename to .NET core/DiscordSharp.Commands/CommandStub.cs
diff --git a/DiscordSharp.Commands/CommandsManager.cs b/.NET core/DiscordSharp.Commands/CommandsManager.cs
similarity index 100%
rename from DiscordSharp.Commands/CommandsManager.cs
rename to .NET core/DiscordSharp.Commands/CommandsManager.cs
diff --git a/.NET core/DiscordSharp.Commands/DiscordSharp.Commands.csproj b/.NET core/DiscordSharp.Commands/DiscordSharp.Commands.csproj
new file mode 100644
index 0000000..a138972
--- /dev/null
+++ b/.NET core/DiscordSharp.Commands/DiscordSharp.Commands.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netcoreapp2.0
+
+
+
+
+
+
+
diff --git a/DiscordSharp.Commands/ICommand.cs b/.NET core/DiscordSharp.Commands/ICommand.cs
similarity index 100%
rename from DiscordSharp.Commands/ICommand.cs
rename to .NET core/DiscordSharp.Commands/ICommand.cs
diff --git a/DiscordSharp.Commands/IDGenerator.cs b/.NET core/DiscordSharp.Commands/IDGenerator.cs
similarity index 100%
rename from DiscordSharp.Commands/IDGenerator.cs
rename to .NET core/DiscordSharp.Commands/IDGenerator.cs
diff --git a/DiscordSharp.Commands/IModule.cs b/.NET core/DiscordSharp.Commands/IModule.cs
similarity index 100%
rename from DiscordSharp.Commands/IModule.cs
rename to .NET core/DiscordSharp.Commands/IModule.cs
diff --git a/DiscordSharp.Commands/Permission.cs b/.NET core/DiscordSharp.Commands/Permission.cs
similarity index 100%
rename from DiscordSharp.Commands/Permission.cs
rename to .NET core/DiscordSharp.Commands/Permission.cs
diff --git a/.NET core/DiscordSharp.sln b/.NET core/DiscordSharp.sln
new file mode 100644
index 0000000..7e4c30c
--- /dev/null
+++ b/.NET core/DiscordSharp.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2010
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp", "DiscordSharp\DiscordSharp.csproj", "{83B812D1-C6A7-4378-825F-F7B21575ADDA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp.Commands", "DiscordSharp.Commands\DiscordSharp.Commands.csproj", "{D0A7320C-33CA-455D-886B-86088C083903}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {83B812D1-C6A7-4378-825F-F7B21575ADDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83B812D1-C6A7-4378-825F-F7B21575ADDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83B812D1-C6A7-4378-825F-F7B21575ADDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83B812D1-C6A7-4378-825F-F7B21575ADDA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0A7320C-33CA-455D-886B-86088C083903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0A7320C-33CA-455D-886B-86088C083903}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0A7320C-33CA-455D-886B-86088C083903}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0A7320C-33CA-455D-886B-86088C083903}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4076345A-687A-4117-890D-F43D786881E1}
+ EndGlobalSection
+EndGlobal
diff --git a/DiscordSharp/Color.cs b/.NET core/DiscordSharp/Color.cs
similarity index 100%
rename from DiscordSharp/Color.cs
rename to .NET core/DiscordSharp/Color.cs
diff --git a/DiscordSharp/DiscordClient.cs b/.NET core/DiscordSharp/DiscordClient.cs
similarity index 87%
rename from DiscordSharp/DiscordClient.cs
rename to .NET core/DiscordSharp/DiscordClient.cs
index a9d8c14..9f44d4c 100644
--- a/DiscordSharp/DiscordClient.cs
+++ b/.NET core/DiscordSharp/DiscordClient.cs
@@ -3,17 +3,17 @@
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 System.Linq;
using DiscordSharp.Objects;
-
-using ID = System.String;
using DiscordSharp.Sockets;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using DiscordSharp.Sockets.BuiltIn;
namespace DiscordSharp
{
@@ -118,6 +118,12 @@ public class DiscordClient
///
public DiscordProperties DiscordProperties { get; set; } = new DiscordProperties();
+ ///
+ /// Custom properties for the default discord channel to listen to
+ /// This updates with various events such as offline/online status
+ ///
+ public string[] DiscordSyncedGuilds { get; set; } = new string[] { };
+
///
/// The current DiscordMember object assosciated with the account you're connected to.
///
@@ -163,11 +169,10 @@ public class DiscordClient
public bool Autoconnect { get; set; } = true;
- private IDiscordWebSocket ws;
+ private NetWebSocketWrapper 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 DiscordVoiceClient VoiceClient;
private Logger DebugLogger = new Logger();
private CancellationTokenSource KeepAliveTaskTokenSource = new CancellationTokenSource();
@@ -197,7 +202,7 @@ public class DiscordClient
/// 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 Dictionary MessageLog = new Dictionary();
//private List> MessageLog = new List>();
private List PrivateChannels = new List();
@@ -236,6 +241,7 @@ public class DiscordClient
public event EventHandler RoleUpdated;
public event EventHandler GuildMemberUpdated;
public event EventHandler GuildMemberBanned;
+ public event EventHandler GuildMembersLoadedEventArgs;
public event EventHandler PrivateChannelDeleted;
public event EventHandler BanRemoved;
public event EventHandler PrivateMessageDeleted;
@@ -278,11 +284,6 @@ public DiscordClient(string tokenOverride = null, bool isBotAccount = false, boo
token = tokenOverride;
IsBotAccount = isBotAccount;
- if (IsBotAccount)
- UserAgentString = "DiscordBot " + UserAgentString;
- else
- UserAgentString = "Custom Discord Client " + UserAgentString;
-
if (ClientPrivateInformation == null)
ClientPrivateInformation = new DiscordUserInformation();
@@ -290,8 +291,7 @@ public DiscordClient(string tokenOverride = null, bool isBotAccount = false, boo
{
if (e.message.Level == MessageLevel.Error)
DisconnectFromVoice();
- if (TextClientDebugMessageReceived != null)
- TextClientDebugMessageReceived(this, e);
+ TextClientDebugMessageReceived?.Invoke(this, e);
};
}
@@ -305,7 +305,7 @@ public DiscordClient(string tokenOverride = null, bool isBotAccount = false, boo
/// 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;
+ public Dictionary GetMessageLog() => MessageLog;
///
/// Private channels assosciated with the account.
@@ -417,11 +417,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();
}
}
}
@@ -433,36 +433,40 @@ private void GetChannelsList(JObject m)
}
if (PrivateChannels == null)
PrivateChannels = new List();
- foreach (var privateChannel in m["d"]["private_channels"])
+ foreach (JObject 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 =>
+ foreach (JObject recepient in privateChannel["recipients"])
{
- if (x.GetMemberByKey(privateChannel["recipient"]["id"].ToString()) != null)
+ DiscordPrivateChannel tempPrivate = JsonConvert.DeserializeObject(privateChannel.ToString());
+ tempPrivate.Client = this;
+ tempPrivate.user_id = recepient["id"].ToString();
+ DiscordServer potentialServer = new DiscordServer();
+ ServersList.ForEach(x =>
{
- potentialServer = x;
- }
- });
- if (potentialServer.Owner != null) //should be a safe test..i hope
- {
- DiscordMember recipient = potentialServer.GetMemberByKey(privateChannel["recipient"]["id"].ToString());
- if (recipient != null)
+ if (x.GetMemberByKey(recepient["id"].ToString()) != null)
+ {
+ potentialServer = x;
+ }
+ });
+ if (potentialServer.Owner != null) //should be a safe test..i hope
{
- tempPrivate.Recipient = recipient;
+ DiscordMember recipient = potentialServer.GetMemberByKey(recepient["id"].ToString());
+ if (recipient != null)
+ {
+ tempPrivate.Recipient = recipient;
+ }
+ else
+ {
+ DebugLogger.Log("Recipient was null!!!!", MessageLevel.Critical);
+ }
}
else
{
- DebugLogger.Log("Recipient was null!!!!", MessageLevel.Critical);
+ DebugLogger.Log("No potential server found for user's private channel null! This will probably fix itself.", MessageLevel.Debug);
}
+ PrivateChannels.Add(tempPrivate);
}
- else
- {
- DebugLogger.Log("No potential server found for user's private channel null! This will probably fix itself.", MessageLevel.Debug);
- }
- PrivateChannels.Add(tempPrivate);
+
}
}
@@ -891,7 +895,7 @@ private DiscordMessage SendActualMessage(string id, string message, DiscordMembe
/// 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)
+ public async void UpdateCurrentGame(string gameName, bool streaming, string url = null)
{
string msg;
if (gameName.ToLower().Trim() != "")
@@ -928,14 +932,14 @@ public void UpdateCurrentGame(string gameName, bool streaming, string url = null
});
DebugLogger.Log("Setting current game to null.");
}
- ws.Send(msg.ToString());
+ await ws.SendMessage(msg.ToString());
}
///
/// Updates the bot's status.
///
/// True if you want the bot to report as idle.
- public void UpdateBotStatus(bool idle)
+ public async void UpdateBotStatus(bool idle)
{
string msg;
msg = JsonConvert.SerializeObject(
@@ -948,7 +952,7 @@ public void UpdateBotStatus(bool idle)
game = CurrentGameName.ToLower().Trim() == "" ? (object)null : new { name = CurrentGameName }
}
});
- ws.Send(msg.ToString()); //let's try it!
+ await ws.SendMessage(msg.ToString()); //let's try it!
}
private void PresenceUpdateEvents(JObject message)
@@ -1159,7 +1163,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;
}
@@ -1192,7 +1196,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;
}
@@ -1211,14 +1215,12 @@ public DiscordMember GetMemberFromChannel(DiscordChannelBase channel, string id)
///
public DiscordChannel GetChannelByName(string channelName)
{
- try
+ DiscordServer findServer = ServersList.Where(x => x.Channels.Find(y => y.Name.ToLower() == channelName.ToLower()) != null).FirstOrDefault();
+ if (findServer != null)
{
- 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;
+ return findServer.Channels.Where(x => x.Name.ToLower() == channelName.ToLower()).FirstOrDefault();
}
+ return null;
}
///
@@ -1228,7 +1230,32 @@ public DiscordChannel GetChannelByName(string channelName)
///
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());
+ foreach (DiscordServer server in ServersList)
+ {
+ foreach (DiscordChannel channel in server.Channels)
+ {
+ if (channel.ID != null && channel.ID == id.ToString())
+ {
+ return channel;
+ }
+ }
+ }
+ return null;
+ }
+
+ public DiscordServer GetServerByChannelID(long id)
+ {
+ foreach (DiscordServer server in ServersList)
+ {
+ foreach (DiscordChannel channel in server.Channels)
+ {
+ if (channel.ID != null && channel.ID == id.ToString())
+ {
+ return server;
+ }
+ }
+ }
+ return null;
}
///
@@ -1335,10 +1362,7 @@ public DiscordMessage EditMessage(string MessageID, string replacementMessage, D
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);
@@ -1349,7 +1373,7 @@ private void SendDeleteRequest(DiscordMessage message)
}
}
- private DiscordMessage FindInMessageLog(ID id)
+ private DiscordMessage FindInMessageLog(String id)
{
foreach (var message in MessageLog)
if (message.Key == id)
@@ -1455,105 +1479,124 @@ private DiscordChannel GetDiscordChannelByID(string id)
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
+ try
{
- 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();
- dpmea.Author = tempMember;
- tempMember.parentclient = this;
- dpmea.RawJson = message;
+ string tempChannelID = message["d"]["channel_id"].ToString();
- if (PrivateMessageReceived != null)
- PrivateMessageReceived(this, dpmea);
+ //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());
+ 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();
+ 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
{
- //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;
+ DiscordMessageEventArgs dmea = new DiscordMessageEventArgs();
+ dmea.RawJson = message;
+ dmea.Channel = potentialChannel;
- dmea.MessageText = message["d"]["content"].ToString();
+ 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);
- }
+ DiscordMember tempMember = null;
+ if (potentialChannel.Parent != null) // Server not sync-ed yet. We need to re-request data
+ {
+ 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;
- dmea.Author = tempMember;
+ potentialChannel.Parent.AddMember(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();
- }
+ 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 (!message["d"]["mentions"].IsNullOrEmpty())
{
- if (MentionReceived != null)
- MentionReceived(this, dmea);
+ 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);
+ KeyValuePair toAdd = new KeyValuePair(message["d"]["id"].ToString(), m);
+ MessageLog.Add(message["d"]["id"].ToString(), m);
- if (MessageReceived != null)
- MessageReceived(this, dmea);
+ MessageReceived?.Invoke(this, dmea);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ DebugLogger.Log(string.Format("Error ocurred during MessageCreateEvents: {0}\r\n{1}", ex.Message, message.ToString()), MessageLevel.Error);
}
- //}
- //catch (Exception ex)
- //{
- // DebugLogger.Log("Error ocurred during MessageCreateEvents: " + ex.Message, MessageLevel.Error);
- //}
}
private void ChannelCreateEvents(JObject message)
{
+ /*{{
+ "t": "CHANNEL_CREATE",
+ "s": 97,
+ "op": 0,
+ "d": {
+ "type": 1,
+ "recipients": [
+ {
+ "username": "",
+ "id": "",
+ "discriminator": "8031",
+ "avatar": ""
+ }
+ ],
+ "last_message_id": null,
+ "id": "496435723788877835"
+ }
+}}*/
if (message["d"]["is_private"].ToString().ToLower() == "false")
{
var foundServer = ServersList.Find(x => x.ID == message["d"]["guild_id"].ToString());
@@ -1569,11 +1612,12 @@ private void ChannelCreateEvents(JObject message)
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);
+
+ ChannelCreated?.Invoke(this, fae);
}
}
else
@@ -1584,9 +1628,14 @@ private void ChannelCreateEvents(JObject message)
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);
+
+ DiscordPrivateChannelEventArgs fak = new DiscordPrivateChannelEventArgs
+ {
+ ChannelType = DiscordChannelCreateType.PRIVATE,
+ ChannelCreated = tempPrivate
+ };
+
+ PrivateChannelCreated?.Invoke(this, fak);
}
}
#endregion
@@ -1863,7 +1912,7 @@ public void DeleteInvite(string id)
///
/// 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)
+ public void Connect()
{
CurrentGatewayURL = GetGatewayUrl();
if (string.IsNullOrEmpty(CurrentGatewayURL))
@@ -1873,41 +1922,31 @@ public void Connect(bool useDotNetWebsocket = false)
}
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 = new NetWebSocketWrapper(CurrentGatewayURL);
ws.MessageReceived += (sender, e) =>
{
- var message = new JObject();
try
{
- message = JObject.Parse(e.Message);
+ var message = JObject.Parse(e.Message);
+
+ 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();
}
- catch(Exception ex)
+ catch (Exception ex)
{
DebugLogger.Log($"MessageReceived Error: {ex.Message}\n\n```{e.Message}\n```\n", MessageLevel.Error);
+ return;
}
-
- 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) =>
{
@@ -1931,9 +1970,10 @@ public void Connect(bool useDotNetWebsocket = false)
DebugLogger.Log("Connecting..");
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MiscellaneousOpcodes(JObject message)
{
- switch (message["d"].ToObject())
+ switch (message["op"].ToObject())
{
case Opcodes.INVALIDATE_SESSION:
// TODO: the session was invalidated and a full reconnection must be performed.
@@ -1956,137 +1996,152 @@ private void PerformReconnection()
});
}
- 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)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private async void ClientPacketReceived(JObject message)
+ {
+ string header = message["t"].ToString();
+
+ Debug.WriteLine(string.Format("[DiscordClient] Received {0} HEADER.", header));
+
+ switch (header)
+ {
+ case "READY":
{
- string wsChunkTest = JsonConvert.SerializeObject(new
+ Sequence = message["s"].ToObject();
+ DiscordGatewayVersion = message["d"]["v"].ToObject();
+ if (message["d"]?["heartbeat_interval"] != null)
+ HeartbeatInterval = message["d"]["heartbeat_interval"].ToObject();
+ BeginHeartbeatTask();
+
+ if (WriteLatestReady)
{
- op = 8,
- d = new
+ using (var sw = new StreamWriter("READY_LATEST.txt"))
{
- guild_id = guildID,
- query = "",
- limit = 0
+ sw.Write(message);
}
- });
- ws.Send(wsChunkTest);
- }
+ }
- ReadyComplete = true;
+ 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();
- Task.Run(() =>
- {
- 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"):
+ //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
+ }
+ });
+ await ws.SendMessage(wsChunkTest);
+
+ //ws.Send(@"{""op"":4,""d"":{ ""guild_id"":null,""channel_id"":null,""self_mute"":true,""self_deaf"":false,""self_video"":false}}");
+ }
+
+ ReadyComplete = true;
+
+ await Task.Run(() =>
+ {
+ 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"):
+ case "GUILD_MEMBER_REMOVE":
GuildMemberRemoveEvents(message);
break;
- case ("GUILD_MEMBER_ADD"):
+ case "GUILD_MEMBER_ADD":
GuildMemberAddEvents(message);
break;
- case ("GUILD_DELETE"):
+ case "GUILD_DELETE":
GuildDeleteEvents(message);
break;
- case ("GUILD_CREATE"):
+ case "GUILD_CREATE":
GuildCreateEvents(message);
break;
- case ("GUILD_MEMBER_UPDATE"):
+ case "GUILD_MEMBER_UPDATE":
GuildMemberUpdateEvents(message);
break;
- case ("GUILD_UPDATE"):
+ case "GUILD_UPDATE":
GuildUpdateEvents(message);
break;
- case ("GUILD_ROLE_DELETE"):
+ case "GUILD_ROLE_DELETE":
GuildRoleDeleteEvents(message);
break;
- case ("GUILD_ROLE_UPDATE"):
+ case "GUILD_ROLE_UPDATE":
GuildRoleUpdateEvents(message);
break;
- case ("PRESENCE_UPDATE"):
+ case "PRESENCE_UPDATE":
PresenceUpdateEvents(message);
break;
- case ("MESSAGE_UPDATE"):
+ case "MESSAGE_UPDATE":
MessageUpdateEvents(message);
break;
- case ("TYPING_START"):
+ 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()) });
+ UserTypingStart?.Invoke(this, new DiscordTypingStartEventArgs { user = uuser, Channel = channel, Timestamp = int.Parse(message["d"]["timestamp"].ToString()) });
}
break;
- case ("MESSAGE_CREATE"):
+ case "MESSAGE_CREATE":
MessageCreateEvents(message);
break;
- case ("CHANNEL_CREATE"):
+ case "CHANNEL_CREATE":
ChannelCreateEvents(message);
break;
- case ("VOICE_STATE_UPDATE"):
+ case "VOICE_STATE_UPDATE":
VoiceStateUpdateEvents(message);
break;
- case ("VOICE_SERVER_UPDATE"):
+ case "VOICE_SERVER_UPDATE":
VoiceServerUpdateEvents(message);
break;
- case ("MESSAGE_DELETE"):
+ case "MESSAGE_DELETE":
MessageDeletedEvents(message);
break;
- case ("USER_UPDATE"):
+ case "USER_UPDATE":
UserUpdateEvents(message);
break;
- case ("CHANNEL_UPDATE"):
+ case "CHANNEL_UPDATE":
ChannelUpdateEvents(message);
break;
- case ("CHANNEL_DELETE"):
+ case "CHANNEL_DELETE":
ChannelDeleteEvents(message);
break;
- case ("GUILD_BAN_ADD"):
+ case "GUILD_BAN_ADD":
GuildMemberBannedEvents(message);
break;
- case ("GUILD_BAN_REMOVE"):
+ case "GUILD_BAN_REMOVE":
GuildMemberBanRemovedEvents(message);
break;
- case ("MESSAGE_ACK"): //ignore this message, it's irrelevant
+ case "MESSAGE_ACK": //ignore this message, it's irrelevant
break;
+ case "SESSIONS_REPLACE": // idk
default:
- if (UnknownMessageTypeReceived != null)
- UnknownMessageTypeReceived(this, new UnknownMessageEventArgs { RawJson = message });
+ UnknownMessageTypeReceived?.Invoke(this, new UnknownMessageEventArgs { RawJson = message });
break;
}
}
- private void SendIdentifyPacket()
+ private async void SendIdentifyPacket()
{
string initJson = JsonConvert.SerializeObject(new
{
@@ -2096,13 +2151,24 @@ private void SendIdentifyPacket()
v = 4,
token = token,
/*large_threshold = 50,*/
- properties = DiscordProperties
+ properties = DiscordProperties,
+
+ large_threshold = 100,
+ synced_guilds = DiscordSyncedGuilds,
+ presence = new
+ {
+ status = "online",
+ since = 0,
+ afk = false,
+ game = "null",
+ },
+ compress = false
}
});
DebugLogger.Log("Sending initJson ( " + initJson + " )");
- ws.Send(initJson);
+ await ws.SendMessage(initJson);
}
private void BeginHeartbeatTask()
@@ -2127,36 +2193,59 @@ 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)
+ JArray membersAsArray = (JArray)message["d"]["members"];
+
+ Parallel.ForEach(membersAsArray, member =>
{
- //if (GuildHasMemberWithID(inServer, member["user"]["id"].ToString()))
- // continue;
- DiscordMember _member = JsonConvert.DeserializeObject(member["user"].ToString());
- if (!member["user"]["roles"].IsNullOrEmpty())
+ string memberId = (string)member["user"]["id"];
+
+ DiscordMember existingMember = inServer.GetMemberByKey((string)member["user"]["id"]);
+
+ if (existingMember == null)
{
- JArray rollsArray = JArray.Parse(member["user"]["roles"].ToString());
- if (rollsArray.Count > 0)
+ existingMember = JsonConvert.DeserializeObject(member["user"].ToString());
+
+ if (!member["roles"].IsNullOrEmpty())
{
- foreach (var rollID in rollsArray)
- _member.Roles.Add(inServer.Roles.Find(x => x.ID == rollID.ToString()));
+ JArray rollsArray = (JArray)member["roles"];
+ if (rollsArray.Count > 0)
+ {
+ foreach (var rollID in rollsArray)
+ {
+ existingMember.Roles.Add(inServer.Roles.Find(x => x.ID == rollID.ToString()));
+ }
+ }
}
+ existingMember.Muted = member["mute"].ToObject();
+ existingMember.Deaf = member["deaf"].ToObject();
+ existingMember.Roles.Add(inServer.Roles.Find(x => x.Name == "@everyone"));
+ existingMember.Status = Status.Offline;
+ existingMember.parentclient = this;
+ existingMember.Parent = inServer;
+ inServer.AddMember(existingMember);
+ }
+ else
+ {
+ // does nothing, already in list
}
- _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);
+ DiscordPrivateChannel _channel = PrivateChannels.Find(x => x.user_id == existingMember.ID);
if (_channel != null)
{
DebugLogger.Log("Found user for private channel!", MessageLevel.Debug);
- _channel.Recipient = _member;
+ _channel.Recipient = existingMember;
}
+ });
+ // Debug.WriteLine("Loaded Member size: " + membersAsArray.Count + " Total size: " + inServer.Members.Count);
+
+ if (membersAsArray.Count != 1000) // chunks of 1000 sent at once.
+ {
+ GuildMembersLoadedEventArgs?.Invoke(this, new DiscordGuildMembersLoadedEventArgs()
+ {
+ Server = inServer,
+ MemberSize = inServer.Members.Count,
+ });
}
}
}
@@ -2168,8 +2257,7 @@ private void GuildMemberBanRemovedEvents(JObject message)
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);
+ BanRemoved?.Invoke(this, e);
}
private void GuildMemberBannedEvents(JObject message)
@@ -2191,8 +2279,7 @@ private void GuildMemberBannedEvents(JObject message)
e.MemberBanned = RemovedMembers.Find(x => x.ID == message["d"]["user"]["id"].ToString());
if (e.MemberBanned != null)
{
- if (GuildMemberBanned != null)
- GuildMemberBanned(this, e);
+ GuildMemberBanned?.Invoke(this, e);
}
else
{
@@ -2209,7 +2296,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();
@@ -2220,14 +2308,12 @@ private void VoiceServerUpdateEvents(JObject message)
VoiceClient.PacketReceived += (sender, e) =>
{
- if (AudioPacketReceived != null)
- AudioPacketReceived(sender, e);
+ AudioPacketReceived?.Invoke(sender, e);
};
VoiceClient.DebugMessageReceived += (sender, e) =>
{
- if (VoiceClientDebugMessageReceived != null)
- VoiceClientDebugMessageReceived(this, e);
+ VoiceClientDebugMessageReceived?.Invoke(this, e);
};
ConnectToVoiceAsync();
@@ -2407,7 +2493,7 @@ public void EchoPacket(DiscordAudioPacket packet)
/// 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)
+ public async 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!");
@@ -2460,7 +2546,7 @@ public void ConnectToVoiceChannel(DiscordChannel channel, DiscordVoiceConfig voi
}
});
- ws.Send(joinVoicePayload);
+ await ws.SendMessage(joinVoicePayload);
}
///
@@ -2487,7 +2573,7 @@ public int ClearOfflineUsersFromServer(DiscordServer server)
///
/// Also disposes
///
- public void DisconnectFromVoice()
+ public async void DisconnectFromVoice()
{
string disconnectMessage = JsonConvert.SerializeObject(new
{
@@ -2508,13 +2594,15 @@ public void DisconnectFromVoice()
VoiceClient = null;
- ws.Send(disconnectMessage);
+ await ws.SendMessage(disconnectMessage);
}
catch
{ }
}
if (ws != null)
- ws.Send(disconnectMessage);
+ {
+ await ws.SendMessage(disconnectMessage);
+ }
VoiceClient = null;
if (VoiceThread != null)
VoiceThread.Abort();
@@ -2541,7 +2629,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 +2641,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()));
@@ -2907,7 +2995,7 @@ private void GuildCreateEvents(JObject message)
server.parentclient = this;
server.ID = message["d"]["id"].ToString();
server.Name = message["d"]["name"].ToString();
- server.Members = new Dictionary();
+ server.Members = new Dictionary();
server.Channels = new List();
server.Roles = new List();
foreach (var roll in message["d"]["roles"])
@@ -2959,7 +3047,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 +3061,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);
@@ -3079,6 +3168,7 @@ private void UserUpdateEvents(JObject message)
//Update members
foreach (var server in ServersList)
{
+
foreach (var member in server.Members)
{
if (member.Value.ID == newMember.ID)
@@ -3210,7 +3300,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()
+ private async void KeepAlive()
{
//string keepAliveJson = "{\"op\":" + Opcodes.HEARTBEAT + ", \"d\":" + Sequence + "}";
string keepAliveJson = JsonConvert.SerializeObject(new
@@ -3220,7 +3310,7 @@ private void KeepAlive()
});
if (ws != null)
{
- ws.Send(keepAliveJson);
+ await ws.SendMessage(keepAliveJson);
KeepAliveSent?.Invoke(this, new DiscordKeepAliveSentEventArgs { SentAt = DateTime.Now, JsonSent = keepAliveJson });
}
}
@@ -3303,6 +3393,8 @@ public string SendLoginRequest()
message += " Email was invalid: " + result["email"];
if (!result["password"].IsNullOrEmpty())
message += " password was invalid: " + result["password"];
+ if (!result["captcha_key"].IsNullOrEmpty())
+ message += " captcha required for this IP address: " + result["captcha_key"].ToString();
throw new DiscordLoginException(message);
}
diff --git a/.NET core/DiscordSharp/DiscordSharp.csproj b/.NET core/DiscordSharp/DiscordSharp.csproj
new file mode 100644
index 0000000..c9e948e
--- /dev/null
+++ b/.NET core/DiscordSharp/DiscordSharp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netcoreapp2.0
+
+
+
+ true
+ true
+
+
+
+ true
+
+
+
+
+
+
+
+
+
diff --git a/DiscordSharp/Endpoints.cs b/.NET core/DiscordSharp/Endpoints.cs
similarity index 100%
rename from DiscordSharp/Endpoints.cs
rename to .NET core/DiscordSharp/Endpoints.cs
diff --git a/DiscordSharp/EpochTime.cs b/.NET core/DiscordSharp/EpochTime.cs
similarity index 100%
rename from DiscordSharp/EpochTime.cs
rename to .NET core/DiscordSharp/EpochTime.cs
diff --git a/DiscordSharp/Events/DiscordBanRemovedEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordBanRemovedEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordBanRemovedEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordBanRemovedEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordChannelCreateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordChannelCreateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordChannelCreateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordChannelCreateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordChannelDeleteEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordChannelDeleteEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordChannelDeleteEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordChannelDeleteEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordChannelUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordChannelUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordChannelUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordChannelUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordConnectEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordConnectEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordConnectEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordConnectEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordDebugMessagesEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordDebugMessagesEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordDebugMessagesEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordDebugMessagesEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildBanEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildBanEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildBanEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildBanEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildCreateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildCreateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildCreateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildCreateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildDeleteEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildDeleteEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildDeleteEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildDeleteEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildMemberAddEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildMemberAddEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildMemberAddEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildMemberAddEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildMemberRemoved.cs b/.NET core/DiscordSharp/Events/DiscordGuildMemberRemoved.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildMemberRemoved.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildMemberRemoved.cs
diff --git a/DiscordSharp/Events/DiscordGuildMemberUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildMemberUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildMemberUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildMemberUpdateEventArgs.cs
diff --git a/.NET core/DiscordSharp/Events/DiscordGuildMembersLoadedEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildMembersLoadedEventArgs.cs
new file mode 100644
index 0000000..d14cf2a
--- /dev/null
+++ b/.NET core/DiscordSharp/Events/DiscordGuildMembersLoadedEventArgs.cs
@@ -0,0 +1,13 @@
+using DiscordSharp.Objects;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DiscordSharp.Events
+{
+ public class DiscordGuildMembersLoadedEventArgs : EventArgs
+ {
+ public DiscordServer Server { get; internal set; }
+ public int MemberSize { get; internal set; }
+ }
+}
diff --git a/DiscordSharp/Events/DiscordGuildRoleDeleteEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildRoleDeleteEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildRoleDeleteEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildRoleDeleteEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordGuildRoleUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordGuildRoleUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordGuildRoleUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordGuildRoleUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordKeepAliveSentEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordKeepAliveSentEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordKeepAliveSentEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordKeepAliveSentEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordLeftVoiceChannelEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordLeftVoiceChannelEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordLeftVoiceChannelEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordLeftVoiceChannelEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordMentionEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordMentionEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordMentionEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordMentionEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordMessageDeletedEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordMessageDeletedEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordMessageDeletedEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordMessageDeletedEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordMessageEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordMessageEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordMessageEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordMessageEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordPresenceUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordPresenceUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordPresenceUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordPresenceUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordPrivateChannelEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordPrivateChannelEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordPrivateChannelEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordPrivateChannelEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordPrivateMessageEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordServerUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordServerUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordServerUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordServerUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordSocketClosedEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordSocketClosedEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordSocketClosedEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordSocketClosedEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordTypingStartEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordTypingStartEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordTypingStartEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordTypingStartEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordURLUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordURLUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordURLUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordURLUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordUserUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordUserUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordUserUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordUserUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordVoiceStateUpdateEventArgs.cs b/.NET core/DiscordSharp/Events/DiscordVoiceStateUpdateEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordVoiceStateUpdateEventArgs.cs
rename to .NET core/DiscordSharp/Events/DiscordVoiceStateUpdateEventArgs.cs
diff --git a/DiscordSharp/Events/DiscordVoiceUserSpeaking.cs b/.NET core/DiscordSharp/Events/DiscordVoiceUserSpeaking.cs
similarity index 100%
rename from DiscordSharp/Events/DiscordVoiceUserSpeaking.cs
rename to .NET core/DiscordSharp/Events/DiscordVoiceUserSpeaking.cs
diff --git a/DiscordSharp/Events/UnknownMessageEventArgs.cs b/.NET core/DiscordSharp/Events/UnknownMessageEventArgs.cs
similarity index 100%
rename from DiscordSharp/Events/UnknownMessageEventArgs.cs
rename to .NET core/DiscordSharp/Events/UnknownMessageEventArgs.cs
diff --git a/DiscordSharp/Extensions.cs b/.NET core/DiscordSharp/Extensions.cs
similarity index 100%
rename from DiscordSharp/Extensions.cs
rename to .NET core/DiscordSharp/Extensions.cs
diff --git a/DiscordSharp/Logger.cs b/.NET core/DiscordSharp/Logger.cs
similarity index 100%
rename from DiscordSharp/Logger.cs
rename to .NET core/DiscordSharp/Logger.cs
diff --git a/DiscordSharp/Objects/DiscordChannel.cs b/.NET core/DiscordSharp/Objects/DiscordChannel.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordChannel.cs
rename to .NET core/DiscordSharp/Objects/DiscordChannel.cs
diff --git a/DiscordSharp/Objects/DiscordLoginException.cs b/.NET core/DiscordSharp/Objects/DiscordLoginException.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordLoginException.cs
rename to .NET core/DiscordSharp/Objects/DiscordLoginException.cs
diff --git a/DiscordSharp/Objects/DiscordLoginInformation.cs b/.NET core/DiscordSharp/Objects/DiscordLoginInformation.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordLoginInformation.cs
rename to .NET core/DiscordSharp/Objects/DiscordLoginInformation.cs
diff --git a/DiscordSharp/Objects/DiscordMember.cs b/.NET core/DiscordSharp/Objects/DiscordMember.cs
similarity index 93%
rename from DiscordSharp/Objects/DiscordMember.cs
rename to .NET core/DiscordSharp/Objects/DiscordMember.cs
index 1d95621..06d7262 100644
--- a/DiscordSharp/Objects/DiscordMember.cs
+++ b/.NET core/DiscordSharp/Objects/DiscordMember.cs
@@ -13,7 +13,10 @@ namespace DiscordSharp.Objects
{
public enum Status
{
- Online, Idle, Offline
+ Online,
+ Idle,
+ Offline,
+ DoNotDisturb
}
public class DiscordMember
{
@@ -52,12 +55,23 @@ Voice only
internal void SetPresence(string status)
{
string checkAgainst = status.ToLower().Trim();
- if (checkAgainst == "online")
- Status = Status.Online;
- else if (checkAgainst == "idle")
- Status = Status.Idle;
- else
- Status = Status.Offline;
+ switch (checkAgainst)
+ {
+ case "dnd":
+ Status = Status.DoNotDisturb;
+ break;
+ case "online":
+ Status = Status.Online;
+ break;
+ case "idle":
+ Status = Status.Idle;
+ break;
+ case "offline":
+ Status = Status.Offline;
+ break;
+ default:
+ throw new Exception("New presence status type found: " + status);
+ }
}
diff --git a/DiscordSharp/Objects/DiscordMessage.cs b/.NET core/DiscordSharp/Objects/DiscordMessage.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordMessage.cs
rename to .NET core/DiscordSharp/Objects/DiscordMessage.cs
diff --git a/DiscordSharp/Objects/DiscordPermission.cs b/.NET core/DiscordSharp/Objects/DiscordPermission.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordPermission.cs
rename to .NET core/DiscordSharp/Objects/DiscordPermission.cs
diff --git a/DiscordSharp/Objects/DiscordPermissionOverride.cs b/.NET core/DiscordSharp/Objects/DiscordPermissionOverride.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordPermissionOverride.cs
rename to .NET core/DiscordSharp/Objects/DiscordPermissionOverride.cs
diff --git a/DiscordSharp/Objects/DiscordRole.cs b/.NET core/DiscordSharp/Objects/DiscordRole.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordRole.cs
rename to .NET core/DiscordSharp/Objects/DiscordRole.cs
diff --git a/DiscordSharp/Objects/DiscordServer.cs b/.NET core/DiscordSharp/Objects/DiscordServer.cs
similarity index 94%
rename from DiscordSharp/Objects/DiscordServer.cs
rename to .NET core/DiscordSharp/Objects/DiscordServer.cs
index 74f5116..d54e0d9 100644
--- a/DiscordSharp/Objects/DiscordServer.cs
+++ b/.NET core/DiscordSharp/Objects/DiscordServer.cs
@@ -79,33 +79,34 @@ internal DiscordServer()
Members = new Dictionary();
}
-
+
internal void AddMember(DiscordMember member)
{
if (member == null)
return;
- if(Members.ContainsKey(member.ID)) //then replace
+
+ lock (Members)
{
- Members.Remove(member.ID);
+ if (Members.ContainsKey(member.ID)) //then replace
+ Members.Remove(member.ID);
+
+ Members.Add(member.ID, member);
}
- Members.Add(member.ID, member);
}
internal int ClearOfflineMembers()
{
- int count = 0;
- foreach(var member in Members)
- {
- if (member.Value.Status == Status.Offline)
- return count;
- }
- return count;
+ return Members.Count(mem => mem.Value.Status == Status.Offline);
}
internal bool RemoveMember(ID key)
{
- if(Members.ContainsKey(key))
+ if (Members.ContainsKey(key))
{
- Members.Remove(key);
+ lock (Members)
+ {
+ Members.Remove(key);
+ }
+ return true;
}
return false;
}
@@ -114,14 +115,11 @@ public DiscordMember GetMemberByKey(ID key)
if (Unavailable)
throw new Exception("Server is currently unavailable!");
- try
- {
- return Members.First(x => x.Key == key).Value;
- }
- catch
+ if (Members.ContainsKey(key))
{
- return null; //because instead of just returning null by default, it has to do this shit.
+ return Members[key];
}
+ return null;
}
public DiscordMember GetMemberByUsername(string username, bool caseSensitive = false)
{
@@ -273,7 +271,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(),
diff --git a/DiscordSharp/Objects/DiscordUserInformation.cs b/.NET core/DiscordSharp/Objects/DiscordUserInformation.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordUserInformation.cs
rename to .NET core/DiscordSharp/Objects/DiscordUserInformation.cs
diff --git a/DiscordSharp/Objects/DiscordVoiceState.cs b/.NET core/DiscordSharp/Objects/DiscordVoiceState.cs
similarity index 100%
rename from DiscordSharp/Objects/DiscordVoiceState.cs
rename to .NET core/DiscordSharp/Objects/DiscordVoiceState.cs
diff --git a/DiscordSharp/Objects/RateLimitException.cs b/.NET core/DiscordSharp/Objects/RateLimitException.cs
similarity index 100%
rename from DiscordSharp/Objects/RateLimitException.cs
rename to .NET core/DiscordSharp/Objects/RateLimitException.cs
diff --git a/DiscordSharp/Opcodes.cs b/.NET core/DiscordSharp/Opcodes.cs
similarity index 100%
rename from DiscordSharp/Opcodes.cs
rename to .NET core/DiscordSharp/Opcodes.cs
diff --git a/.NET core/DiscordSharp/Sockets/BuiltIn/NetWebSocketWrapper.cs b/.NET core/DiscordSharp/Sockets/BuiltIn/NetWebSocketWrapper.cs
new file mode 100644
index 0000000..6c71bd8
--- /dev/null
+++ b/.NET core/DiscordSharp/Sockets/BuiltIn/NetWebSocketWrapper.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Sockets.BuiltIn
+{
+ public class NetWebSocketWrapper
+ {
+ private const int ReceiveChunkSize = 4096;
+
+ private string _URL;
+ public string URL
+ {
+ get { return _URL; }
+ private set { }
+ }
+
+ private readonly ClientWebSocket _ws;
+ private readonly Uri _uri;
+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private readonly CancellationToken _cancellationToken;
+
+ ///
+ /// Create an instance
+ ///
+ ///
+ public NetWebSocketWrapper(string uri)
+ {
+ this._URL = uri;
+
+ _ws = new ClientWebSocket();
+ _ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
+ _uri = new Uri(uri);
+ _cancellationToken = _cancellationTokenSource.Token;
+
+ //_ws.CloseStatusDescription
+ //_ws.CloseStatus.Value.ToString();
+ }
+
+ #region Public methods
+ public event EventHandler MessageReceived;
+ public event EventHandler SocketClosed;
+ public event EventHandler SocketError;
+ public event EventHandler SocketOpened;
+
+ public bool IsAlive
+ {
+ get
+ {
+ return _ws != null;
+ }
+ }
+
+ ///
+ /// Connects to the WebSocket server.
+ ///
+ public void Connect()
+ {
+ //await _ws.ConnectAsync(_uri, _cancellationToken);
+ _ws.ConnectAsync(_uri, _cancellationToken).Wait();
+
+ SocketOpened?.Invoke(this, null);
+
+ StartListen();
+ }
+
+ ///
+ /// Close socket
+ ///
+ public void Close()
+ {
+ CallOnDisconnected("User requested to exit.");
+ }
+
+ ///
+ /// Send a message to the WebSocket server.
+ ///
+ /// The message to send
+ /// success
+ public async Task SendMessage(string message)
+ {
+ byte[] messageBuffer = Encoding.UTF8.GetBytes(message);
+ return await SendMessage(messageBuffer);
+ }
+
+ ///
+ /// Sends a byte array to the connected socket
+ ///
+ ///
+ /// success
+ public async Task SendMessage(byte[] buffer)
+ {
+ if (_ws.State != WebSocketState.Open)
+ {
+ throw new Exception("Connection is not open.");
+ }
+ try
+ {
+ await _ws.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
+ return true;
+ }
+ catch (Exception exp)
+ {
+ CallOnDisconnected(exp.Message);
+ }
+ return false;
+ }
+ #endregion
+
+ private async void StartListen()
+ {
+ try
+ {
+ while (_ws != null && _ws.State == WebSocketState.Open)
+ {
+ byte[] Sharedbuffer = new byte[ReceiveChunkSize];
+
+
+ using (var ms = new MemoryStream()) // auto release memory
+ {
+ WebSocketReceiveResult res;
+ do
+ {
+ res = await _ws.ReceiveAsync(Sharedbuffer, CancellationToken.None);
+ if (res.MessageType == WebSocketMessageType.Close)
+ {
+ CallOnDisconnected(null);
+ return;
+ }
+ ms.Write(Sharedbuffer, 0, res.Count);
+ // ms.Write(segment.Array, segment.Offset, res.Count);
+ }
+ while (!res.EndOfMessage);
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ // Return data
+ byte[] returnBuffer = new byte[ms.Length];
+ Buffer.BlockCopy(ms.ToArray(), 0, returnBuffer, 0, (int)ms.Length);
+
+ string msg = Encoding.UTF8.GetString(returnBuffer);
+
+ // Fires the return packet in a new thread
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ SocketMessageEventArgs args = new SocketMessageEventArgs
+ {
+ Message = msg
+ };
+ MessageReceived?.Invoke(this, args);
+ });
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.StackTrace);
+ CallOnDisconnected(ex.Message);
+ }
+ finally
+ {
+ _ws.Dispose();
+ }
+ }
+
+ private void CallOnDisconnected(string messageOverride)
+ {
+ try
+ {
+ _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait();
+ }
+ catch { }
+
+ SocketClosedEventArgs args = new SocketClosedEventArgs
+ {
+ Reason = messageOverride != null ? messageOverride : _ws.CloseStatusDescription,
+ WasClean = false,
+ Code = _ws.CloseStatus != null ? (int)_ws.CloseStatus.Value : -1
+ };
+ SocketClosed?.Invoke(this, args);
+ }
+ }
+}
diff --git a/.NET core/DiscordSharp/Sockets/IDiscordWebSocket.cs b/.NET core/DiscordSharp/Sockets/IDiscordWebSocket.cs
new file mode 100644
index 0000000..95bfc71
--- /dev/null
+++ b/.NET core/DiscordSharp/Sockets/IDiscordWebSocket.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Sockets
+{
+ public class SocketMessageEventArgs : EventArgs
+ {
+ public string Message { get; internal set; }
+ }
+
+ public class SocketClosedEventArgs : EventArgs
+ {
+ public string Reason { get; internal set; }
+ public int Code { get; internal set; }
+ public bool WasClean { get; internal set; }
+ }
+
+ public class SocketErrorEventArgs : EventArgs
+ {
+ public Exception Exception { get; internal set; }
+ public string Message { get; internal set; }
+ }
+
+
+}
diff --git a/DiscordSharp/Utils.cs b/.NET core/DiscordSharp/Utils.cs
similarity index 100%
rename from DiscordSharp/Utils.cs
rename to .NET core/DiscordSharp/Utils.cs
diff --git a/DiscordSharp/Voice/DiscordAudioPacket.cs b/.NET core/DiscordSharp/Voice/DiscordAudioPacket.cs
similarity index 100%
rename from DiscordSharp/Voice/DiscordAudioPacket.cs
rename to .NET core/DiscordSharp/Voice/DiscordAudioPacket.cs
diff --git a/.NET core/DiscordSharp/Voice/DiscordVoiceClient.cs b/.NET core/DiscordSharp/Voice/DiscordVoiceClient.cs
new file mode 100644
index 0000000..50b3660
--- /dev/null
+++ b/.NET core/DiscordSharp/Voice/DiscordVoiceClient.cs
@@ -0,0 +1,969 @@
+using DiscordSharp.Events;
+using DiscordSharp.Objects;
+using DiscordSharp.Voice;
+using Microsoft.Win32.SafeHandles;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using WebSocket4Net;
+#if NETFX4_5
+using ws4n.WebSocket4Net;
+using System.Collections.Concurrent;
+#else
+using CONCURRENT = System.Collections.Concurrent;
+#endif
+
+/**
+ This file contains the methods necessary for creating a Discord voice connection.
+
+ To any future library developers looking to implement voice, this is how the basic recipe for voice communication goes with Discord.
+
+ Ingredients Needed:
+ 1. A decent UdpClient
+ 2. A websocket that doesn't suck. (different from your main one)
+ 3. An understanding of bitshifting.
+
+ To begin the voice connection:
+ 1. On your main websocket, send json with opcode 4, the guild_id, and channel_id, and whether or not you want your client to muted/deaf.
+ 2. After that, you will get a websocket message with t = "VOICE_SERVER_UPDATE".
+ 2a. The json you receive will contain some IMPORTANT information including voice endpoint (for socket) and a token you will need.
+ 3. Now, you can initiate your voice client.
+ 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.
+ 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.
+ 6. Now, you should be safe to begin your UDP connection. Point it to the endpoint you used to connect to voice initially BUT do **NOT** use the default port! Use the port that is given to you in opcode 2.
+ 6a. Also, don't include the wss://
+ 7. Once connected, send a 70 byte packet containing your ssrc as bigendian for the first 4 bytes.
+ 8. Your response should be a packet. This packet contains your IP address and port you need. Please use the IP from this packet.
+ 8a. Quoth thy wise Danny: "the ip is ascii starting at the 4th byte and ending at the first null"
+ 8b. the port is a little endian unsigned short in the last two bytes
+ yes, this is different endianness from everything else
+ 9. You will then send opcode 1 with your protocol (more than likely udp), your IP from the packet, the port from the packet, and a mode. More than likely "plain"
+ 10. Finally, once you receive opcode 4, you may send the "speaking" json (opcode 5, speaking = true/false, delay = 0). At this point, you are fully connected and you may begin any other threads/keepalives.
+*/
+
+namespace DiscordSharp
+{
+ 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;
+ public int port;
+ }
+
+ 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; }
+ }
+
+ 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.
+ ///
+ public int Channels { get; set; } = 1;
+
+ ///
+ /// The frame length, in ms, of audio you'll be sending at one time.
+ /// As a rule of thumb, you generally want to set it to something above your current Discord ping.
+ ///
+ public int FrameLengthMs { get; set; } = 60;
+
+ ///
+ /// The voice client now auto-configures this based on the channel.
+ ///
+ [Obsolete]
+ public int? Bitrate { get; set; } = null;
+
+ ///
+ /// The mode the Opus encoder will use.
+ /// MusicOrMixed is generally the best.
+ ///
+ public OpusApplication OpusMode { get; set; } = OpusApplication.MusicOrMixed;
+
+ ///
+ /// If true, the voice client will only send voice and will not receive it.
+ ///
+ public bool SendOnly { get; set; } = true;
+
+ ///
+ /// The blocksize of PCM data you should be reading and piping into DiscordSharp.
+ ///
+ public int PCMBlockSize
+ {
+ get
+ {
+ return ((SampleRate / 1000) * 2 * Channels * FrameLengthMs);
+ }
+ }
+ }
+
+ public class DiscordVoiceClient : IDisposable
+ {
+ private DiscordClient _parent;
+
+ public bool Connected { get; internal set; }
+ public string SessionID { get; internal set; }
+ public string VoiceEndpoint { get; internal set; }
+ public string Token { get; internal set; }
+ public DiscordChannel Channel { get; internal set; }
+ public DiscordServer Guild { get; internal set; }
+ public DiscordMember Me { get; internal set; }
+ private DiscordMember LastSpoken { get; set; }
+ private UdpClient _udp = new UdpClient();
+ private VoiceConnectionParameters Params { get; set; }
+ private Logger VoiceDebugLogger = new Logger();
+ private WebSocket VoiceWebSocket;
+ private OpusEncoder mainOpusEncoder;
+ private CancellationTokenSource globalTaskSource = new CancellationTokenSource();
+ //private CONCURRENT.ConcurrentQueue voiceToSend = new CONCURRENT.ConcurrentConcurrentQueue();\
+#if NETFX4_5
+ private ConcurrentQueue voiceToSend = new ConcurrentQueue();
+#else
+ private CONCURRENT.ConcurrentQueue voiceToSend = new CONCURRENT.ConcurrentQueue();
+#endif
+ private List MembersInChannel = new List();
+ private Dictionary SsrcDictionary = new Dictionary();
+ private string encryptionMode = "xsalsa20_poly1305";
+ private byte[] __secretKey;
+ private Thread VoiceSendThread, VoiceReceiveThread, UDPKeepAliveThread, WebsocketKeepAliveThread;
+
+ private IPEndPoint udpEndpoint;
+
+ public OpusDecoder Decoder { get; private set; }
+ 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
+ ///
+ /// The length, in ms, of audio for DiscordSharp to send. By default, 20.
+ ///
+ static int msToSend = 20;
+
+ ///
+ /// An signed short designating the sequence of the audio being sent.
+ ///
+ internal ushort ___sequence = 0;
+
+ ///
+ /// Unsigned int designating the timestamp offset of the audio being sent.
+ ///
+ internal uint ___timestamp = 0;
+#endregion
+
+ public Logger GetDebugLogger => VoiceDebugLogger;
+
+ public DiscordVoiceClient(DiscordClient parentClient)
+ {
+ _parent = parentClient;
+ VoiceConfig = new DiscordVoiceConfig();
+ InitializeOpusEncoder();
+ }
+
+ public DiscordVoiceClient(DiscordClient parentClient, DiscordVoiceConfig config)
+ {
+ _parent = parentClient;
+ VoiceConfig = config;
+ InitializeOpusEncoder();
+ }
+
+ public DiscordVoiceClient(DiscordClient parentClient, DiscordVoiceConfig config, DiscordChannel channel)
+ {
+ _parent = parentClient;
+ VoiceConfig = config;
+ Channel = channel;
+ InitializeOpusEncoder();
+ }
+
+ internal void InitializeOpusEncoder()
+ {
+ if (Channel != null && Channel.Type == ChannelType.Voice)
+ {
+ if (Channel.Bitrate > 0)
+ {
+ mainOpusEncoder = new OpusEncoder(VoiceConfig.SampleRate,
+ VoiceConfig.Channels,
+ VoiceConfig.FrameLengthMs,
+ (Channel.Bitrate / 1000),
+ VoiceConfig.OpusMode);
+ }
+ else
+ {
+ mainOpusEncoder = new OpusEncoder(VoiceConfig.SampleRate,
+ VoiceConfig.Channels,
+ VoiceConfig.FrameLengthMs,
+ null,
+ VoiceConfig.OpusMode);
+ }
+
+ mainOpusEncoder.SetForwardErrorCorrection(true);
+ msToSend = VoiceConfig.FrameLengthMs;
+ }
+ if(!VoiceConfig.SendOnly)
+ InitializeOpusDecoder();
+ }
+
+ private void InitializeOpusDecoder()
+ {
+ if(Channel != null && Channel.Type == ChannelType.Voice)
+ {
+ Decoder = new OpusDecoder(VoiceConfig.SampleRate, VoiceConfig.Channels, VoiceConfig.FrameLengthMs);
+ }
+ }
+
+ ///
+ /// Begins the voice client connection.
+ ///
+ public void Initiate()
+ {
+ if(Me == null)
+ {
+ if (_parent != null)
+ {
+ Me = _parent.Me;
+ }
+ else
+ throw new NullReferenceException("VoiceClient's main client reference was null!");
+ }
+
+ MembersInChannel.Add(Me);
+
+ VoiceDebugLogger.LogMessageReceived += (sender, e) =>
+ {
+ if (DebugMessageReceived != null)
+ DebugMessageReceived(this, e);
+ };
+
+ VoiceWebSocket = new WebSocket(VoiceEndpoint.StartsWith("wss://") ? VoiceEndpoint.Replace(":80", "") :
+ "wss://" + VoiceEndpoint.Replace(":80", ""));
+ VoiceWebSocket.EnableAutoSendPing = false;
+
+ //VoiceWebSocket.AllowUnstrustedCertificate = true; // TODO
+ VoiceWebSocket.NoDelay = true;
+ VoiceWebSocket.Closed += VoiceWebSocket_OnClose;
+ VoiceWebSocket.Error += VoiceWebSocket_OnError;
+
+ VoiceWebSocket.MessageReceived += (s, e) =>
+ {
+ try
+ {
+ VoiceWebSocket_OnMessage(s, e);
+ }
+ catch(Exception ex)
+ {
+ VoiceDebugLogger.Log($"Exception while awaiting OnMessage?!\n\tMessage: {ex.Message}\n\tStack: {ex.StackTrace}", MessageLevel.Critical);
+ }
+ };
+ VoiceWebSocket.Opened += (sender, e) =>
+ {
+ string initMsg = JsonConvert.SerializeObject(new
+ {
+ op = 0,
+ d = new
+ {
+ server_id = Guild.ID,
+ user_id = Me.ID,
+ session_id = SessionID,
+ token = Token
+ }
+ });
+
+ VoiceDebugLogger.Log("VoiceWebSocket opened, sending initial json. ( " + initMsg + ") ");
+
+ VoiceWebSocket.Send(initMsg);
+ };
+
+ VoiceWebSocket.Open();
+ }
+
+#pragma warning disable 4014 //stupid await warnings
+ private void VoiceWebSocket_OnMessage(object sender, MessageReceivedEventArgs e)
+ {
+ JObject message = JObject.Parse(e.Message);
+ switch (int.Parse(message["op"].ToString()))
+ {
+ case 2:
+ //VoiceDebugLogger.Log(e.Message);
+ OpCode2(message);
+ //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);
+ SendVoiceTask(globalTaskSource.Token);
+ if (!VoiceConfig.SendOnly)
+ ReceiveVoiceTask(globalTaskSource.Token);
+ SetSpeaking(true);
+ 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
+ internal void MemberRemoved(DiscordMember removed)
+ {
+ if (MembersInChannel.Contains(removed))
+ {
+ MembersInChannel.Remove(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.CurrentVoiceChannel == Channel)
+ {
+ if (MembersInChannel.Contains(added))
+ MembersInChannel.Remove(added);
+ MembersInChannel.Add(added);
+ VoiceDebugLogger.Log($"User {added.Username} ({added.ID}) joined the client's current connected voice channel.");
+ }
+ }
+ }
+ private void OpCode5(JObject message)
+ {
+ DiscordVoiceUserSpeakingEventArgs e = new DiscordVoiceUserSpeakingEventArgs();
+ e.Channel = Channel;
+ e.UserSpeaking = Guild.GetMemberByKey(message["d"]["user_id"].ToString());
+ e.Speaking = message["d"]["speaking"].ToObject();
+ e.Ssrc = message["d"]["ssrc"].ToObject();
+
+ if(e.UserSpeaking != null)
+ {
+ if (!SsrcDictionary.ContainsKey(e.UserSpeaking))
+ SsrcDictionary.Add(e.UserSpeaking, e.Ssrc);
+ }
+
+ LastSpoken = e.UserSpeaking;
+
+ if (UserSpeaking != null)
+ UserSpeaking(this, e);
+ }
+
+ private void OpCode4(JObject message)
+ {
+ __secretKey = message["d"]["secret_key"].ToObject();
+ string speakingJson = JsonConvert.SerializeObject(new
+ {
+ op = 5,
+ d = new
+ {
+ speaking = true,
+ delay = 0
+ }
+ });
+ VoiceDebugLogger.Log("Sending initial speaking json..( " + speakingJson + " )");
+ VoiceWebSocket.Send(speakingJson);
+ //we are officially connected!!!
+ Connected = true;
+ }
+
+#pragma warning disable 4014
+ private void OpCode2(JObject message)
+ {
+ Params = JsonConvert.DeserializeObject(message["d"].ToString());
+ SsrcDictionary.Add(Me, Params.ssrc);
+ for (int i = 0; i < Params.modes.Length; i++)
+ {
+ if (!Params.modes[i].ToLower().Contains("plain"))
+ {
+ encryptionMode = Params.modes[i];
+ break;
+ }
+ }
+ //SendWebSocketKeepalive();
+ DoWebSocketKeepAlive(globalTaskSource.Token);
+ }
+#pragma warning restore 4014
+
+ private void VoiceWebSocket_OnError(object sender, EventArgs e)
+ {
+ VoiceDebugLogger.Log("Error in VoiceWebSocket.", MessageLevel.Critical);
+ ErrorReceived?.Invoke(this, new EventArgs());
+
+ //Won't worror about on error for now
+ }
+
+ private void VoiceWebSocket_OnClose(object sender, EventArgs e)
+ {
+ VoiceDebugLogger.Log($"VoiceWebSocket was closed.", MessageLevel.Critical);
+ ErrorReceived?.Invoke(this, new EventArgs());
+
+ Dispose();
+ }
+#endregion
+
+ private bool QueueEmptyEventTriggered = false;
+#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
+ {
+#if NETFX4_5
+ //await Task.Delay(1000).ConfigureAwait(false);
+ Thread.Sleep(1000);
+#else
+ Thread.Sleep(1000);
+#endif
+ }
+ if (___sequence > 0 || ___timestamp > 0)
+ {
+ if (voiceToSend.IsEmpty)
+ {
+ //reset these
+ ___sequence = 0;
+ ___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.");
+#if NETFX4_5
+ return Task.Run(async () =>
+#else
+ return Task.Factory.StartNew(async () =>
+#endif
+ {
+ while(!token.IsCancellationRequested)
+ {
+ if (_udp.Available > 0)
+ {
+ try
+ {
+ await DoReceiveVoice().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ VoiceDebugLogger.Log($"Exception in receive loop: {ex.Message}\n\t{ex.StackTrace}", MessageLevel.Error);
+ }
+ }
+ }
+ }, token);
+ }
+
+ private async Task DoReceiveVoice()
+ {
+ _udp.DontFragment = false;
+ if (_udp.Available > 0)
+ {
+ //the packet received, the 4000 size buffer for decoding, the nonce header for encryption and the decrypted/decoded result :)
+ byte[] packet, decodingBuffer = null, nonce = null, result;
+ //UdpReceiveResult receivedResult = await _udp.ReceiveAsync().ConfigureAwait(false);
+ packet = _udp.Receive(ref udpEndpoint);
+ int packetLength, resultOffset, resultLength;
+ decodingBuffer = new byte[4000];
+ nonce = new byte[24];
+ packetLength = packet.Length;
+
+ if (packet.Length > 0)
+ {
+ if (packetLength < 12) return; //irrelevant packet
+ if (packet[0] != 0x80) return; //flags
+ if (packet[1] != 0x78) return; //payload type. you know, from before.
+
+ ushort sequenceNumber = (ushort)((packet[2] << 8) | packet[3] << 0);
+ uint timDocuestamp = (uint)((packet[4] << 24) | packet[5] << 16 | packet[6] << 8 | packet[7] << 0);
+ uint ssrc = (uint)((packet[8] << 24) | (packet[9] << 16) | (packet[10] << 8) | (packet[11] << 0));
+
+ //encryption is enabled by default
+ if (packetLength < 28) return; //irrelevant packet
+
+ Buffer.BlockCopy(packet, 0, nonce, 0, 12); //copy nonce
+ var length = Convert.ToUInt64(packetLength - 12);
+ int returnValue = SecretBox.Decrypt(packet, 12, length, decodingBuffer, nonce, __secretKey);
+ if (returnValue != 0)
+ return;
+ result = decodingBuffer;
+ resultOffset = 0; resultLength = packetLength - 28;
+
+ if (SsrcDictionary.ContainsValue((int)ssrc))
+ {
+ if (PacketReceived != null)
+ {
+ PacketReceived(this, new DiscordAudioPacketEventArgs
+ {
+ Channel = this.Channel,
+ FromUser = GetUserBySsrc(ssrc),
+ OpusAudio = result,
+ OpusAudioLength = resultLength
+ });
+ }
+ }
+
+ }
+ }
+ }
+
+ private DiscordMember GetUserBySsrc(uint ssrc)
+ {
+ foreach (var user in SsrcDictionary)
+ if (user.Value == ssrc)
+ return user.Key;
+
+ return null;
+ }
+
+#pragma warning restore 4014
+ private void SendVoiceAsync(CancellationToken cancelToken)
+ {
+ byte[] opusAudio; //pcm data
+ voiceToSend.TryDequeue(out opusAudio);
+ if (opusAudio != null)
+ {
+ Stopwatch timeToSend = Stopwatch.StartNew();
+
+ byte[] fullVoicePacket = new byte[4000 + 12 + 16];
+ byte[] nonce = new byte[24];
+
+ fullVoicePacket[0] = (byte)0x80; //flags
+ fullVoicePacket[1] = (byte)0x78; //flags
+
+ fullVoicePacket[8] = (byte)((Params.ssrc >> 24) & 0xFF); //ssrc
+ fullVoicePacket[9] = (byte)((Params.ssrc >> 16) & 0xFF); //ssrc
+ fullVoicePacket[10] = (byte)((Params.ssrc >> 8) & 0xFF); //ssrc
+ fullVoicePacket[11] = (byte)((Params.ssrc >> 0) & 0xFF); //ssrc
+
+ //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;
+
+ //actual sending
+ {
+ ___sequence = unchecked(___sequence++);
+ //sequence big endian
+ fullVoicePacket[2] = (byte)((___sequence >> 8));
+ fullVoicePacket[3] = (byte)((___sequence >> 0) & 0xFF);
+
+ //timestamp big endian
+ fullVoicePacket[4] = (byte)((___timestamp >> 24) & 0xFF);
+ fullVoicePacket[5] = (byte)((___timestamp >> 16) & 0xFF);
+ fullVoicePacket[6] = (byte)((___timestamp >> 8));
+ fullVoicePacket[7] = (byte)((___timestamp >> 0) & 0xFF);
+
+ Buffer.BlockCopy(fullVoicePacket, 0, nonce, 0, 12); //copy header into nonce
+
+ //Buffer.BlockCopy(rtpPacket, 2, nonce, 2, 6); //copy 6 bytes for nonce
+ int returnVal = SecretBox.Encrypt(opusAudio, encodedLength, fullVoicePacket, 12, nonce, __secretKey);
+ if (returnVal != 0)
+ return;
+ if (opusAudio == null)
+ throw new ArgumentNullException("opusAudio");
+
+ int maxSize = encodedLength;
+ int rtpPacketLength = encodedLength + 12 + 16;
+
+#if NETFX4_5
+ //dataSent = _udp.SendAsync(fullVoicePacket, encodedLength + 12 + 16).Result;
+ dataSent = _udp.Send(fullVoicePacket, encodedLength + 12 + 16);
+#else
+ dataSent = _udp.Send(fullVoicePacket, rtpPacketLength);
+#endif
+
+ ___timestamp = unchecked(___timestamp + (uint)((opusAudio.Length - 4) / 2));
+ }
+
+ timeToSend.Stop(); //stop after completely sending
+
+ //Compensate for however long it took to sent.
+ if (timeToSend.ElapsedMilliseconds > 0)
+ {
+ //long timeToWait = (msToSend * TimeSpan.TicksPerMillisecond) - (timeToSend.ElapsedMilliseconds * TimeSpan.TicksPerMillisecond);
+ //long timeToWait = (long)(msToSend * TimeSpan.TicksPerMillisecond * 0.80) - (timeToSend.ElapsedMilliseconds * TimeSpan.TicksPerMillisecond);
+ if (timeToSend.ElapsedMilliseconds > 0) //if it's negative then don't bother waiting
+ {
+ //Thread.Sleep((int)(msToSend - timeToSend.ElapsedMilliseconds));
+ Thread.Sleep((int)Math.Floor(msToSend * 0.80));
+ }
+ }
+ else
+ {
+ Thread.Sleep(msToSend);
+ }
+
+ VoiceDebugLogger.LogAsync("Sent " + dataSent + " bytes of Opus audio", MessageLevel.Unecessary);
+ }
+ }
+
+ private void DoWebSocketKeepAlive(CancellationToken token)
+ {
+ WebsocketKeepAliveThread = new Thread(() =>
+ {
+ try
+ {
+ while (VoiceWebSocket.State == WebSocketState.Open && !token.IsCancellationRequested)
+ {
+ if (VoiceWebSocket != null)
+ {
+ if (VoiceWebSocket.State == WebSocketState.Open)
+ {
+ string keepAliveJson = JsonConvert.SerializeObject(new
+ {
+ op = 3,
+ d = EpochTime.GetMilliseconds()
+ });
+ VoiceDebugLogger.Log("Sending voice keepalive ( " + keepAliveJson + " ) ", MessageLevel.Unecessary);
+ VoiceWebSocket.Send(keepAliveJson);
+ Thread.Sleep(Params.heartbeat_interval);
+ }
+ }
+ }
+ }
+ catch (NullReferenceException) { }
+ });
+ WebsocketKeepAliveThread.Start();
+ }
+
+ private void DoUDPKeepAlive(CancellationToken token)
+ {
+
+ UDPKeepAliveThread = new Thread(() =>
+ {
+ byte[] keepAlive = new byte[5];
+ keepAlive[0] = (byte)0xC9;
+ try
+ {
+ long seq = 0;
+ while (VoiceWebSocket.State == WebSocketState.Open && !token.IsCancellationRequested)
+ {
+ if (token.IsCancellationRequested)
+ break;
+ keepAlive[1] = (byte)((___sequence >> 24) & 0xFF);
+ keepAlive[2] = (byte)((___sequence >> 16) & 0xFF);
+ keepAlive[3] = (byte)((___sequence >> 8) & 0xFF);
+ keepAlive[4] = (byte)((___sequence >> 0) & 0xFF);
+ _udp.Send(keepAlive, keepAlive.Length);
+ VoiceDebugLogger.Log("Sent UDP keepalive.", MessageLevel.Unecessary);
+ Thread.Sleep(5 * 1000);
+ }
+ }
+ catch (ObjectDisposedException)
+ {/*cancel token disposed*/}
+ catch (NullReferenceException)
+ {/*disposed*/}
+ catch (Exception ex)
+ {
+ VoiceDebugLogger.Log($"Error sending UDP keepalive\n\t{ex.Message}\n\t{ex.StackTrace}", MessageLevel.Error);
+ }
+ });
+ UDPKeepAliveThread.Start();
+ }
+ private async Task InitialUDPConnection()
+ {
+ try
+ {
+ _udp = new UdpClient(Params.port); //passes in proper port
+ //_udp.
+ _udp.DontFragment = false;
+ _udp.Connect(VoiceEndpoint.Replace(":80", ""), Params.port);
+
+ VoiceDebugLogger.Log($"Initialized UDP Client at {VoiceEndpoint.Replace(":80", "")}:{Params.port}");
+
+ 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);
+ packet[2] = (byte)((Params.ssrc >> 8) & 0xFF);
+ packet[3] = (byte)((Params.ssrc >> 0) & 0xFF);
+
+#if NETFX4_5
+ await _udp.SendAsync(packet, packet.Length).ConfigureAwait(false); //sends this initial packet.
+ VoiceDebugLogger.Log("Sent ssrc packet.");
+
+ UdpReceiveResult resultingMessage = await _udp.ReceiveAsync().ConfigureAwait(false); //receive a response packet
+ Console.WriteLine($"Receiving");
+#else
+ _udp.Send(packet, packet.Length, udpEndpoint);
+ VoiceDebugLogger.Log("Sent ssrc packet.");
+
+ byte[] resultingMessage = _udp.Receive(ref udpEndpoint);
+#endif
+
+#if NETFX4_5
+ if (resultingMessage != null && resultingMessage.Buffer.Length > 0)
+#else
+ if(resultingMessage != null && resultingMessage.Length > 0)
+#endif
+ {
+ VoiceDebugLogger.Log("Received IP packet, reading..");
+#if NETFX4_5
+ await SendIPOverUDP(GetIPAndPortFromPacket(resultingMessage.Buffer)).ConfigureAwait(false);
+#else
+ var ipAndPort = GetIPAndPortFromPacket(resultingMessage);
+ udpEndpoint = new IPEndPoint(ipAndPort.Address, ipAndPort.port);
+ await SendIPOverUDP(ipAndPort).ConfigureAwait(false);
+#endif
+ }
+ else
+ VoiceDebugLogger.Log("No IP packet received.", MessageLevel.Critical);
+ }
+ catch (Exception ex)
+ {
+ 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
+ ///
+ /// The byte[] returned after sending your ssrc.
+ ///
+ private async Task SendIPOverUDP(DiscordIpPort ipPort)
+ {
+ string msg = JsonConvert.SerializeObject(new
+ {
+ op = 1,
+ d = new
+ {
+ protocol = "udp",
+ data = new
+ {
+ address = ipPort.Address.ToString(),
+ port = ipPort.port,
+ mode = encryptionMode
+ }
+ }
+ });
+ VoiceDebugLogger.Log("Sending our IP over WebSocket ( " + msg.ToString() + " ) ");
+ VoiceWebSocket.Send(msg);
+ //await Task.Run(() => VoiceWebSocket.Send(msg)).ConfigureAwait(false); //idk lets try it
+ }
+
+ private DiscordIpPort GetIPAndPortFromPacket(byte[] packet)
+ {
+ DiscordIpPort returnVal = new DiscordIpPort();
+ //quoth thy danny
+ //#the ip is ascii starting at the 4th byte and ending at the first null
+ int startingIPIndex = 4;
+ int endingIPIndex = 4;
+ for (int i = startingIPIndex; i < packet.Length; i++)
+ {
+ if (packet[i] != (byte)0)
+ endingIPIndex++;
+ else
+ break;
+ }
+
+ byte[] ipArray = new byte[endingIPIndex - startingIPIndex];
+ Buffer.BlockCopy(packet, startingIPIndex, ipArray, 0, ipArray.Length);
+ //quoth thy wise danny part two:
+ //# the port is a little endian unsigned short in the last two bytes
+ //# yes, this is different endianness from everything else
+ int port = packet[packet.Length - 2] | packet[packet.Length - 1] << 8;
+
+ returnVal.Address = IPAddress.Parse(System.Text.Encoding.ASCII.GetString(ipArray));
+ returnVal.port = port;
+
+ VoiceDebugLogger.Log($"Our IP is {returnVal.Address} and we're using port {returnVal.port}.");
+ return returnVal;
+ }
+#endregion
+
+#region Public Methods
+ ///
+ /// Enqueues audio to be sent through the UDP client.
+ ///
+ /// PCM audio data.
+ public void SendVoice(byte[] voice)
+ {
+ byte[] opusAudio = new byte[voice.Length + 4];
+ int encodedLength = mainOpusEncoder.EncodeFrame(voice, 0, opusAudio);
+
+ byte[] len = BitConverter.GetBytes(encodedLength);
+ opusAudio[voice.Length] = len[0];
+ opusAudio[voice.Length + 1] = len[1];
+ opusAudio[voice.Length + 2] = len[2];
+ opusAudio[voice.Length + 3] = len[3];
+
+ voiceToSend.Enqueue(opusAudio);
+ }
+
+ ///
+ /// Clears the voice queue thus stopping the audio.
+ ///
+ public void ClearVoiceQueue()
+ {
+ byte[] ignored;
+ while (voiceToSend.TryDequeue(out ignored)) ;
+ }
+
+ ///
+ /// Echos a given DiscordAudioPacket.
+ ///
+ ///
+ ///
+ public async Task EchoPacket(DiscordAudioPacket packet)
+ {
+ await SendPacket(DiscordAudioPacket.EchoPacket(packet.AsRawPacket(), Params.ssrc)).ConfigureAwait(false);
+ }
+ ///
+ /// Sends a given DiscordAudioPacket over the UDP client..
+ ///
+ ///
+ ///
+ public async Task SendPacket(DiscordAudioPacket packet)
+ {
+ if (_udp != null && VoiceWebSocket.State == WebSocketState.Open)
+ {
+#if NETFX4_5
+ await _udp.SendAsync(packet.AsRawPacket(), packet.AsRawPacket().Length).ConfigureAwait(false);
+#else
+ _udp.Send(packet.AsRawPacket(), packet.AsRawPacket().Length);
+#endif
+ VoiceDebugLogger.Log("Sent packet through SendPacket task.", MessageLevel.Unecessary);
+ }
+ }
+ ///
+ /// Whether or not the current client is set to be speaking.
+ ///
+ /// If true, you will be set to speaking.
+ public void SetSpeaking(bool speaking)
+ {
+ if (VoiceWebSocket != null)
+ {
+ if (VoiceWebSocket.State == WebSocketState.Open)
+ {
+ string speakingJson = JsonConvert.SerializeObject(new
+ {
+ op = 5,
+ d = new
+ {
+ speaking = speaking,
+ delay = 0
+ }
+ });
+ VoiceDebugLogger.Log("Sending voice speaking ( " + speakingJson + " ) ", MessageLevel.Unecessary);
+ VoiceWebSocket.Send(speakingJson);
+ }
+ else
+ VoiceDebugLogger.Log("VoiceWebSocket not alive?", MessageLevel.Critical);
+ }
+ else
+ VoiceDebugLogger.Log("VoiceWebSocket null?", MessageLevel.Critical);
+ }
+#endregion
+
+#region Cleanup
+ bool disposed = false;
+ SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+ if(disposing)
+ {
+ handle.Dispose();
+
+ VoiceSendThread.Abort();
+ UDPKeepAliveThread.Abort();
+ WebsocketKeepAliveThread.Abort();
+
+ Connected = false;
+ if (VoiceWebSocket != null)
+ {
+ VoiceWebSocket.Closed -= VoiceWebSocket_OnClose;
+ VoiceWebSocket.Error -= VoiceWebSocket_OnError;
+ VoiceWebSocket.Close();
+ }
+ VoiceWebSocket = null;
+ globalTaskSource.Cancel(false);
+ globalTaskSource.Dispose();
+ if (_udp != null)
+ _udp.Close();
+ _udp = null;
+ }
+ disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+
+ if (Disposed != null)
+ Disposed(this, new EventArgs());
+ }
+#endregion
+ }
+}
diff --git a/DiscordSharp/Voice/OpusConverter.cs b/.NET core/DiscordSharp/Voice/OpusConverter.cs
similarity index 100%
rename from DiscordSharp/Voice/OpusConverter.cs
rename to .NET core/DiscordSharp/Voice/OpusConverter.cs
diff --git a/DiscordSharp/Voice/OpusDecoder.cs b/.NET core/DiscordSharp/Voice/OpusDecoder.cs
similarity index 100%
rename from DiscordSharp/Voice/OpusDecoder.cs
rename to .NET core/DiscordSharp/Voice/OpusDecoder.cs
diff --git a/DiscordSharp/Voice/OpusEncoder.cs b/.NET core/DiscordSharp/Voice/OpusEncoder.cs
similarity index 100%
rename from DiscordSharp/Voice/OpusEncoder.cs
rename to .NET core/DiscordSharp/Voice/OpusEncoder.cs
diff --git a/DiscordSharp/Voice/Sodium.cs b/.NET core/DiscordSharp/Voice/Sodium.cs
similarity index 100%
rename from DiscordSharp/Voice/Sodium.cs
rename to .NET core/DiscordSharp/Voice/Sodium.cs
diff --git a/DiscordSharp/WebWrapper.cs b/.NET core/DiscordSharp/WebWrapper.cs
similarity index 100%
rename from DiscordSharp/WebWrapper.cs
rename to .NET core/DiscordSharp/WebWrapper.cs
diff --git a/.DS_Store b/.NET framework/.DS_Store
similarity index 100%
rename from .DS_Store
rename to .NET framework/.DS_Store
diff --git a/.travis.yml b/.NET framework/.travis.yml
similarity index 100%
rename from .travis.yml
rename to .NET framework/.travis.yml
diff --git a/.NET framework/DiscordSharp.Commands/CommandStub.cs b/.NET framework/DiscordSharp.Commands/CommandStub.cs
new file mode 100644
index 0000000..61f37fc
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/CommandStub.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DiscordSharp.Objects;
+
+namespace DiscordSharp.Commands
+{
+ ///
+ /// A basic command providing arguments as strings.
+ ///
+ public class CommandStub : ICommand
+ {
+ internal override Type __typeofCommand
+ {
+ get
+ {
+ return typeof(CommandStub);
+ }
+ set
+ {
+ base.__typeofCommand = value;
+ }
+ }
+
+ internal CommandStub()
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+ }
+
+ public CommandStub(string name, string description, string helpTag)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ CommandName = name;
+ Description = description;
+ HelpTag = helpTag;
+
+ Args = new List();
+ }
+
+ public CommandStub(string name, string description)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ CommandName = name;
+ Description = description;
+
+ Args = new List();
+ }
+
+ public CommandStub(Action action)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ Do = action;
+
+ Args = new List();
+ }
+
+ public CommandStub(string name, string description, Action action)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ Do = action;
+ CommandName = name;
+ Description = description;
+
+ Args = new List();
+ }
+
+ public CommandStub(string name, string description, string helpTag, Action action)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ Do = action;
+ CommandName = name;
+ Description = description;
+ HelpTag = helpTag;
+
+ Args = new List();
+ }
+
+ public CommandStub(string name, string description, string helpTag, PermissionType minPerm, Action action)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ Do = action;
+ CommandName = name;
+ Description = description;
+ HelpTag = helpTag;
+ MinimumPermission = minPerm;
+
+ Args = new List();
+ }
+
+ public CommandStub(string name, string description, string helpTag, PermissionType minPerm, int argCount, Action action)
+ {
+ this.ID = IDGenerator.GenerateRandomCode();
+
+ Do = action;
+ CommandName = name;
+ Description = description;
+ HelpTag = helpTag;
+ MinimumPermission = minPerm;
+ ArgCount = argCount;
+
+ Args = new List();
+ }
+
+ [Obsolete]
+ public override void ExecuteCommand()
+ {
+ CommandArgs e = new CommandArgs();
+ e.Args = this.Args;
+ Do.Invoke(e);
+ }
+
+ public override void ExecuteCommand(DiscordChannel channel, DiscordMember member)
+ {
+ CommandArgs e = new CommandArgs();
+ e.Args = this.Args;
+ e.Author = member;
+ e.Channel = channel;
+
+ if ((int)CommandsManager.GetPermissionFromID(member.ID) >= (int)MinimumPermission)
+ Do.Invoke(e);
+ else
+ throw new UnauthorizedAccessException($"You have no permission to execute this command! (Minimum needed: {(MinimumPermission.ToString().Substring(MinimumPermission.ToString().IndexOf('.') + 1))})");
+ }
+ }
+}
diff --git a/.NET framework/DiscordSharp.Commands/CommandsManager.cs b/.NET framework/DiscordSharp.Commands/CommandsManager.cs
new file mode 100644
index 0000000..9ad7eeb
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/CommandsManager.cs
@@ -0,0 +1,265 @@
+using DiscordSharp.Objects;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Commands
+{
+ public class ModuleNotEnabledException : Exception
+ {
+ IModule module;
+ public IModule Module
+ {
+ get { return module; }
+ }
+ public ModuleNotEnabledException(string message, IModule module) : base(message)
+ {
+ this.module = module;
+ }
+ }
+
+ public class BaseModuleToggleException : Exception
+ {
+ public BaseModuleToggleException(string message) : base(message) { }
+ }
+
+ public class CommandsManager
+ {
+ private readonly DiscordClient __client;
+ public DiscordClient Client
+ {
+ get
+ {
+ return __client;
+ }
+ }
+
+ public Random rng = new Random((int)DateTime.Now.Ticks);
+
+ private List __commands;
+ public List Commands
+ {
+ get { return __commands; }
+ }
+
+ ///
+ /// Key value pair of the modules.
+ /// Key = module
+ /// Value = Whether or not the module is enabled.
+ ///
+ private Dictionary __modules;
+ public Dictionary Modules
+ {
+ get { return __modules; }
+ }
+
+ //id, permission
+ private static Dictionary __internalUserRoles;
+ public static Dictionary UserRoles
+ {
+ get
+ {
+ return __internalUserRoles;
+ }
+ }
+
+ internal static PermissionType GetPermissionFromID(string id)
+ {
+ if (__internalUserRoles.Count > 0)
+ {
+ foreach(var perm in __internalUserRoles)
+ {
+ if (perm.Key == id)
+ return perm.Value;
+ }
+ return PermissionType.User;
+ }
+ else
+ return PermissionType.User;
+ }
+
+ public CommandsManager(DiscordClient client)
+ {
+ __client = client;
+ __commands = new List();
+ __modules = new Dictionary();
+ __internalUserRoles = new Dictionary();
+ Console.Write("");
+ }
+
+ public bool HasPermission(DiscordMember member, PermissionType permission)
+ {
+ if(__internalUserRoles.ContainsKey(member.ID))
+ {
+ foreach (var perm in __internalUserRoles)
+ if (perm.Key == member.ID && (int)perm.Value >= (int)permission)
+ return true;
+ }
+ return false;
+ }
+
+ public void AddPermission(DiscordMember member, PermissionType permission)
+ {
+ if (__internalUserRoles.ContainsKey(member.ID))
+ __internalUserRoles.Remove(member.ID);
+ __internalUserRoles.Add(member.ID, permission);
+ }
+ public void AddPermission(string memberID, PermissionType permission)
+ {
+ if (__internalUserRoles.ContainsKey(memberID))
+ __internalUserRoles.Remove(memberID);
+ __internalUserRoles.Add(memberID, permission);
+ }
+
+ public void OverridePermissionsDictionary(Dictionary dict) => __internalUserRoles = dict;
+
+ public void OverrideModulesDictionary(Dictionary dictionary)
+ {
+ foreach (IModule kvp in __modules.Keys.ToList())
+ {
+ if (kvp.Name.ToLower().Trim() != "base")
+ {
+ if (dictionary.ContainsKey(kvp.Name.ToLower().Trim()))
+ __modules[kvp] = dictionary[kvp.Name.ToLower().Trim()];
+ }
+ }
+ }
+
+ public Dictionary ModuleDictionaryForJson()
+ {
+ Dictionary dict = new Dictionary();
+
+ lock(__modules)
+ {
+ foreach (var kvp in __modules)
+ {
+ if (kvp.Key.Name.ToLower().Trim() != "base")
+ dict.Add(kvp.Key.Name, kvp.Value);
+ }
+ }
+
+ return dict;
+ }
+
+ public int ExecuteOnMessageCommand(string rawCommandText, DiscordChannel channel, DiscordMember author)
+ {
+ string[] split = rawCommandText.Split(new char[] { ' ' }); //splits into args and stuff
+ try
+ {
+ var command = __commands.Find(x => x.CommandName == split[0]);
+
+ if (command != null && command.Parent != null) //if it's a generic command without a parent then don't bother doing this.
+ {
+ lock(__modules)
+ {
+ if (__modules[command.Parent] == false)
+ {
+ throw new ModuleNotEnabledException($"The specified module {command.Parent.Name} is not enabled.", command.Parent);
+ }
+ }
+ }
+
+ if(command != null)
+ {
+ command.Args.Clear();
+ if (command.ArgCount > 0)
+ {
+ string[] argsSplit = rawCommandText.Split(new char[] { ' ' }, command.ArgCount + 1);
+ //adds all the arguments
+ for (int i = 1; i < argsSplit.Length; i++)
+ command.AddArgument(argsSplit[i]);
+ }
+ //finally, executes it
+ command.ExecuteCommand(channel, author);
+ return 0;
+ }
+ }
+ catch(UnauthorizedAccessException uaex)
+ {
+ throw uaex; //no permission
+ }
+ catch(Exception ex)
+ {
+ throw ex;
+ }
+ return 1;
+ }
+
+ public bool ModuleEnabled(string name)
+ {
+ lock(__modules)
+ {
+ foreach (var kvp in __modules)
+ {
+ if (kvp.Key.Name.ToLower().Trim() == name.ToLower().Trim())
+ {
+ return __modules[kvp.Key];
+ }
+ }
+ }
+ return false;
+ }
+
+ public void EnableModule(string name)
+ {
+ lock(__modules)
+ {
+ foreach (var kvp in __modules)
+ {
+ if (kvp.Key.Name.ToLower().Trim() == name.ToLower().Trim()) //if module exists
+ {
+ __modules[kvp.Key] = true; //enabled
+ break;
+ }
+ }
+ }
+ }
+
+ public void DisableModule(string name)
+ {
+ if (name.ToLower().Trim() == "base")
+ throw new BaseModuleToggleException("Can't disable base module!");
+
+ lock(__modules)
+ {
+ foreach (var kvp in __modules)
+ {
+ if (kvp.Key.Name.ToLower().Trim() == name.ToLower().Trim()) //if module exists
+ {
+ __modules[kvp.Key] = false; //disable it
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Adds a generic command without an associated module.
+ ///
+ ///
+ public void AddCommand(ICommand command) => __commands.Add(command);
+
+ ///
+ /// Adds a command with an assosciated module.
+ ///
+ ///
+ ///
+ public void AddCommand(ICommand command, IModule fromModule)
+ {
+ command.Parent = fromModule;
+ command.Parent.Commands.Add(command);
+ lock(__modules)
+ {
+ if (!__modules.ContainsKey(fromModule))
+ __modules.Add(fromModule, true);
+
+ if (__modules[fromModule] == false) //if you're adding the command, you're enabling the module.
+ __modules[fromModule] = true;
+ }
+
+ __commands.Add(command);
+ }
+ }
+}
diff --git a/DiscordSharp.Commands/DiscordSharp.Commands.csproj b/.NET framework/DiscordSharp.Commands/DiscordSharp.Commands.csproj
similarity index 92%
rename from DiscordSharp.Commands/DiscordSharp.Commands.csproj
rename to .NET framework/DiscordSharp.Commands/DiscordSharp.Commands.csproj
index a07a430..fc913bf 100644
--- a/DiscordSharp.Commands/DiscordSharp.Commands.csproj
+++ b/.NET framework/DiscordSharp.Commands/DiscordSharp.Commands.csproj
@@ -1,5 +1,5 @@
-
+
Debug
@@ -9,8 +9,9 @@
Properties
DiscordSharp.Commands
DiscordSharp.Commands
- v4.5
+ v4.6.1
512
+
true
diff --git a/.NET framework/DiscordSharp.Commands/ICommand.cs b/.NET framework/DiscordSharp.Commands/ICommand.cs
new file mode 100644
index 0000000..23bfeb0
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/ICommand.cs
@@ -0,0 +1,70 @@
+using DiscordSharp.Objects;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Commands
+{
+ public class CommandArgs
+ {
+ public List Args { get; internal set; }
+ public DiscordChannel Channel { get; internal set; }
+ public DiscordMember Author { get; internal set; }
+ }
+
+ public abstract class ICommand
+ {
+ ///
+ /// The trigger of the command you want to run.
+ /// Example: help, random, die, 8ball
+ ///
+ public virtual string CommandName { get; set; }
+
+ ///
+ /// A short description of the command.
+ ///
+ public virtual string Description { get; set; }
+
+ ///
+ /// A help tag for using the command
+ ///
+ public virtual string HelpTag { get; set; }
+
+ ///
+ /// The arguments this command can take.
+ ///
+ public virtual List Args { get; set; }
+
+ public virtual int ArgCount { get; set; }
+
+ ///
+ /// The module this command came from.
+ ///
+ public virtual IModule Parent { get; internal set; }
+
+ ///
+ /// The permission type that the command takes.
+ ///
+ public virtual PermissionType MinimumPermission { get; set; } = PermissionType.User;
+
+ public virtual Action Do { get; internal set; }
+
+ internal virtual Type __typeofCommand { get; set; }
+ public virtual string ID { get; set; }
+
+ public abstract void ExecuteCommand();
+ public abstract void ExecuteCommand(DiscordChannel channel, DiscordMember member);
+
+ //public string ReturnArgument(string argName)
+ //{
+ // return Args.Select(m => m).Where(x => x.Key == argName).Select(k => k.Value).First();
+ //}
+
+ public void AddArgument(string argValue)
+ {
+ Args.Add(argValue);
+ }
+ }
+}
diff --git a/.NET framework/DiscordSharp.Commands/IDGenerator.cs b/.NET framework/DiscordSharp.Commands/IDGenerator.cs
new file mode 100644
index 0000000..094bbde
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/IDGenerator.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace DiscordSharp.Commands
+{
+ public class IDGenerator
+ {
+ private static Random AlphaGenerator = new Random(DateTime.Now.Millisecond);
+ private static Random NumericalGenerator = new Random((int)(DateTime.Now.Millisecond * 98734));
+
+ private static String[] Alphabet = new string[] { "a", "b", "c", "d", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
+
+ internal IDGenerator() { }
+
+ public static string GenerateRandomCode()
+ {
+ List code = new List();
+ for (int i = 0; i < 25; i++)
+ {
+ int letterOrNum = NumericalGenerator.Next(0, 9);
+ if (IsOdd(letterOrNum)) //alpha
+ {
+ string letter = Alphabet[AlphaGenerator.Next(0, Alphabet.Length - 1)];
+ if (code.Count > 0)
+ while (letter == code[i - 1])
+ letter = Alphabet[AlphaGenerator.Next(0, Alphabet.Length - 1)];
+ code.Add(letter);
+ }
+ else //num
+ {
+ int num = NumericalGenerator.Next(0, 9);
+ if (code.Count > 0)
+ while (num.ToString() == code[i - 1])
+ num = NumericalGenerator.Next(0, 9);
+ code.Add(num.ToString());
+ }
+ }
+
+
+ return ArrayToString(code.ToArray());
+ }
+
+ private static string ArrayToString(String[] array)
+ {
+ string code = "";
+ for (int i = 0; i < array.Length - 1; i++)
+ {
+ code += array[i];
+ }
+ return code;
+ }
+
+ private static bool IsOdd(int num)
+ {
+ return num % 2 != 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/.NET framework/DiscordSharp.Commands/IModule.cs b/.NET framework/DiscordSharp.Commands/IModule.cs
new file mode 100644
index 0000000..8d3b2c6
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/IModule.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Commands
+{
+ public abstract class IModule
+ {
+ ///
+ /// The name of the module.
+ ///
+ public virtual string Name { get; set; } = "module";
+
+ ///
+ /// A description talking about what the module contains
+ ///
+ public virtual string Description { get; set; } = "Please set this in the constructor of your IModule derivative.";
+
+ ///
+ /// A list of the commands this module contains
+ ///
+ public virtual List Commands { get; internal set; } = new List();
+
+ ///
+ /// Installs the module's commands into the commands manager
+ ///
+ ///
+ public abstract void Install(CommandsManager manager);
+
+ ///
+ /// Uninstall's this modules's commands from the given module manager.
+ ///
+ ///
+ public void Uninstall(CommandsManager manager)
+ {
+ lock (manager.Commands)
+ {
+ foreach (var command in manager.Commands)
+ {
+ var thisModulesCommand = Commands.Find(x => x.ID == command.ID && x.Parent.Name == this.Name); //compare modules by name just in case
+ if (thisModulesCommand != null)
+ manager.Commands.Remove(command);
+ }
+ }
+ }
+
+ }
+}
diff --git a/.NET framework/DiscordSharp.Commands/Permission.cs b/.NET framework/DiscordSharp.Commands/Permission.cs
new file mode 100644
index 0000000..098b028
--- /dev/null
+++ b/.NET framework/DiscordSharp.Commands/Permission.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp.Commands
+{
+ public enum PermissionType : int
+ {
+ Owner = 666,
+ Admin = 2,
+ Mod = 1,
+ User = 0,
+ None = -666
+ }
+}
diff --git a/DiscordSharp.Commands/Properties/AssemblyInfo.cs b/.NET framework/DiscordSharp.Commands/Properties/AssemblyInfo.cs
similarity index 100%
rename from DiscordSharp.Commands/Properties/AssemblyInfo.cs
rename to .NET framework/DiscordSharp.Commands/Properties/AssemblyInfo.cs
diff --git a/DiscordSharp.sln b/.NET framework/DiscordSharp.sln
similarity index 98%
rename from DiscordSharp.sln
rename to .NET framework/DiscordSharp.sln
index c396374..dda7796 100644
--- a/DiscordSharp.sln
+++ b/.NET framework/DiscordSharp.sln
@@ -1,104 +1,104 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp", "DiscordSharp\DiscordSharp.csproj", "{A96FFE9E-3650-4976-872E-5BB336CC1589}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luigibot", "DiscordSharpTestApplication\Luigibot.csproj", "{02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp.Commands", "DiscordSharp.Commands\DiscordSharp.Commands.csproj", "{2E99D97F-3480-43E2-AC9D-7DAE521CB610}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- DebugDLL|Any CPU = DebugDLL|Any CPU
- DebugDLL|x64 = DebugDLL|x64
- DebugDLL|x86 = DebugDLL|x86
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- ReleaseDLL|Any CPU = ReleaseDLL|Any CPU
- ReleaseDLL|x64 = ReleaseDLL|x64
- ReleaseDLL|x86 = ReleaseDLL|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25123.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp", "DiscordSharp\DiscordSharp.csproj", "{A96FFE9E-3650-4976-872E-5BB336CC1589}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luigibot", "DiscordSharpTestApplication\Luigibot.csproj", "{02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp.Commands", "DiscordSharp.Commands\DiscordSharp.Commands.csproj", "{2E99D97F-3480-43E2-AC9D-7DAE521CB610}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ DebugDLL|Any CPU = DebugDLL|Any CPU
+ DebugDLL|x64 = DebugDLL|x64
+ DebugDLL|x86 = DebugDLL|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ ReleaseDLL|Any CPU = ReleaseDLL|Any CPU
+ ReleaseDLL|x64 = ReleaseDLL|x64
+ ReleaseDLL|x86 = ReleaseDLL|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/DiscordSharp.sln.save b/.NET framework/DiscordSharp.sln.save
similarity index 98%
rename from DiscordSharp.sln.save
rename to .NET framework/DiscordSharp.sln.save
index 4bf573c..e719fbc 100644
--- a/DiscordSharp.sln.save
+++ b/.NET framework/DiscordSharp.sln.save
@@ -1,150 +1,150 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp", "DiscordSharp\DiscordSharp.csproj", "{A96FFE9E-3650-4976-872E-5BB336CC1589}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luigibot", "DiscordSharpTestApplication\Luigibot.csproj", "{02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandsTest", "VoiceCaptureTest\CommandsTest.csproj", "{8CD30B38-9D45-4E49-94F9-7A48977CDA49}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp.Commands", "DiscordSharp.Commands\DiscordSharp.Commands.csproj", "{2E99D97F-3480-43E2-AC9D-7DAE521CB610}"
-EndProject
-bal
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- DebugDLL|Any CPU = DebugDLL|Any CPU
- DebugDLL|x64 = DebugDLL|x64
- DebugDLL|x86 = DebugDLL|x86
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- ReleaseDLL|Any CPU = ReleaseDLL|Any CPU
- ReleaseDLL|x64 = ReleaseDLL|x64
- ReleaseDLL|x86 = ReleaseDLL|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x64.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x64.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x86.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x86.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|Any CPU.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x64.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x64.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x86.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x86.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.Build.0 = Debug|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.Build.0 = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
- {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.Build.0 = Release|Any CPU
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|Any CPU.ActiveCfg = Debug|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x64.ActiveCfg = Debug|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x64.Build.0 = Debug|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x86.ActiveCfg = Debug|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x86.Build.0 = Debug|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|Any CPU.ActiveCfg = DebugDLL|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x86.ActiveCfg = DebugDLL|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x86.Build.0 = DebugDLL|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|Any CPU.ActiveCfg = Release|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x64.ActiveCfg = Release|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x64.Build.0 = Release|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x86.ActiveCfg = Release|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x86.Build.0 = Release|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|Any CPU.ActiveCfg = ReleaseDLL|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x86.ActiveCfg = ReleaseDLL|Win32
- {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x86.Build.0 = ReleaseDLL|Win32
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp", "DiscordSharp\DiscordSharp.csproj", "{A96FFE9E-3650-4976-872E-5BB336CC1589}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luigibot", "DiscordSharpTestApplication\Luigibot.csproj", "{02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandsTest", "VoiceCaptureTest\CommandsTest.csproj", "{8CD30B38-9D45-4E49-94F9-7A48977CDA49}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordSharp.Commands", "DiscordSharp.Commands\DiscordSharp.Commands.csproj", "{2E99D97F-3480-43E2-AC9D-7DAE521CB610}"
+EndProject
+bal
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ DebugDLL|Any CPU = DebugDLL|Any CPU
+ DebugDLL|x64 = DebugDLL|x64
+ DebugDLL|x86 = DebugDLL|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ ReleaseDLL|Any CPU = ReleaseDLL|Any CPU
+ ReleaseDLL|x64 = ReleaseDLL|x64
+ ReleaseDLL|x86 = ReleaseDLL|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x64.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Debug|x86.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x64.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.Release|x86.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {A96FFE9E-3650-4976-872E-5BB336CC1589}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x64.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Debug|x86.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x64.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.Release|x86.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {02AA3864-7A3D-45E4-92DE-C6DAE8972ABF}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x64.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Debug|x86.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x64.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x64.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x86.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.Release|x86.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {8CD30B38-9D45-4E49-94F9-7A48977CDA49}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x64.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Debug|x86.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x64.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.DebugDLL|x86.Build.0 = Debug|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x64.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.Release|x86.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x64.Build.0 = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
+ {2E99D97F-3480-43E2-AC9D-7DAE521CB610}.ReleaseDLL|x86.Build.0 = Release|Any CPU
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x64.ActiveCfg = Debug|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x64.Build.0 = Debug|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x86.ActiveCfg = Debug|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Debug|x86.Build.0 = Debug|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|Any CPU.ActiveCfg = DebugDLL|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x64.Build.0 = DebugDLL|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x86.ActiveCfg = DebugDLL|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.DebugDLL|x86.Build.0 = DebugDLL|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|Any CPU.ActiveCfg = Release|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x64.ActiveCfg = Release|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x64.Build.0 = Release|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x86.ActiveCfg = Release|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.Release|x86.Build.0 = Release|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|Any CPU.ActiveCfg = ReleaseDLL|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x86.ActiveCfg = ReleaseDLL|Win32
+ {A185B162-6CB6-4502-B03F-B56F7699A8D9}.ReleaseDLL|x86.Build.0 = ReleaseDLL|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/.NET framework/DiscordSharp/Color.cs b/.NET framework/DiscordSharp/Color.cs
new file mode 100644
index 0000000..f087699
--- /dev/null
+++ b/.NET framework/DiscordSharp/Color.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscordSharp
+{
+ ///
+ /// Custom color class just because.
+ ///
+ public class Color
+ {
+ public uint R { get; set; }
+ public uint G { get; set; }
+ public uint B { get; set; }
+
+ private uint raw;
+
+ public Color(string hex)
+ {
+ uint asActualHex = Convert.ToUInt32(hex, 16);
+
+ //01 23 45 67
+ //FF FF FF FF
+ R = (uint)(asActualHex >> 16) & 0xFF;
+ G = (uint)(asActualHex >> 8) & 0xFF;
+ B = (uint)(asActualHex & 0xFF);
+ //A = (asActualHex >> 24) & 0xFF
+ raw = asActualHex;
+ }
+
+ public int ToDecimal()
+ {
+ return (int)raw;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0:X}", raw);
+ }
+ }
+}
diff --git a/.NET framework/DiscordSharp/DiscordClient.cs b/.NET framework/DiscordSharp/DiscordClient.cs
new file mode 100644
index 0000000..9f44d4c
--- /dev/null
+++ b/.NET framework/DiscordSharp/DiscordClient.cs
@@ -0,0 +1,3414 @@
+using System;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using System.Net;
+using System.IO;
+using System.Threading;
+using System.Collections.Generic;
+using Newtonsoft.Json.Linq;
+using DiscordSharp.Events;
+using System.Drawing;
+using System.Linq;
+using DiscordSharp.Objects;
+using DiscordSharp.Sockets;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using DiscordSharp.Sockets.BuiltIn;
+
+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();
+
+ ///
+ /// Custom properties for the default discord channel to listen to
+ /// This updates with various events such as offline/online status
+ ///
+ public string[] DiscordSyncedGuilds { get; set; } = new string[] { };
+
+ ///
+ /// 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 NetWebSocketWrapper ws;
+ private List ServersList { get; set; }
+ private string CurrentGameName = "";
+ private int? IdleSinceUnixTime = null;
+ 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 GuildMembersLoadedEventArgs;
+ 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
+
+ ///
+ ///
+ ///
+ /// 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 (ClientPrivateInformation == null)
+ ClientPrivateInformation = new DiscordUserInformation();
+
+ DebugLogger.LogMessageReceived += (sender, e) =>
+ {
+ if (e.message.Level == MessageLevel.Error)
+ DisconnectFromVoice();
+ TextClientDebugMessageReceived?.Invoke(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 (JObject privateChannel in m["d"]["private_channels"])
+ {
+ foreach (JObject recepient in privateChannel["recipients"])
+ {
+ DiscordPrivateChannel tempPrivate = JsonConvert.DeserializeObject(privateChannel.ToString());
+ tempPrivate.Client = this;
+ tempPrivate.user_id = recepient["id"].ToString();
+ DiscordServer potentialServer = new DiscordServer();
+ ServersList.ForEach(x =>
+ {
+ if (x.GetMemberByKey(recepient["id"].ToString()) != null)
+ {
+ potentialServer = x;
+ }
+ });
+ if (potentialServer.Owner != null) //should be a safe test..i hope
+ {
+ DiscordMember recipient = potentialServer.GetMemberByKey(recepient["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 async 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.");
+ }
+ await ws.SendMessage(msg.ToString());
+ }
+
+ ///
+ /// Updates the bot's status.
+ ///
+ /// True if you want the bot to report as idle.
+ public async 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 }
+ }
+ });
+ await ws.SendMessage(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)
+ {
+ DiscordServer findServer = ServersList.Where(x => x.Channels.Find(y => y.Name.ToLower() == channelName.ToLower()) != null).FirstOrDefault();
+ if (findServer != null)
+ {
+ return findServer.Channels.Where(x => x.Name.ToLower() == channelName.ToLower()).FirstOrDefault();
+ }
+ return null;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public DiscordChannel GetChannelByID(long id)
+ {
+ foreach (DiscordServer server in ServersList)
+ {
+ foreach (DiscordChannel channel in server.Channels)
+ {
+ if (channel.ID != null && channel.ID == id.ToString())
+ {
+ return channel;
+ }
+ }
+ }
+ return null;
+ }
+
+ public DiscordServer GetServerByChannelID(long id)
+ {
+ foreach (DiscordServer server in ServersList)
+ {
+ foreach (DiscordChannel channel in server.Channels)
+ {
+ if (channel.ID != null && channel.ID == id.ToString())
+ {
+ return server;
+ }
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// (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;
+ 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(String 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());
+ 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();
+ 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;
+ if (potentialChannel.Parent != null) // Server not sync-ed yet. We need to re-request data
+ {
+ 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);
+
+ MessageReceived?.Invoke(this, dmea);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ DebugLogger.Log(string.Format("Error ocurred during MessageCreateEvents: {0}\r\n{1}", ex.Message, message.ToString()), MessageLevel.Error);
+ }
+ }
+
+ private void ChannelCreateEvents(JObject message)
+ {
+ /*{{
+ "t": "CHANNEL_CREATE",
+ "s": 97,
+ "op": 0,
+ "d": {
+ "type": 1,
+ "recipients": [
+ {
+ "username": "",
+ "id": "",
+ "discriminator": "8031",
+ "avatar": ""
+ }
+ ],
+ "last_message_id": null,
+ "id": "496435723788877835"
+ }
+}}*/
+ 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;
+
+ ChannelCreated?.Invoke(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
+ };
+
+ PrivateChannelCreated?.Invoke(this, fak);
+ }
+ }
+ #endregion
+ 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()
+ {
+ CurrentGatewayURL = GetGatewayUrl();
+ if (string.IsNullOrEmpty(CurrentGatewayURL))
+ {
+ DebugLogger.Log("Gateway URL was null or empty?!", MessageLevel.Critical);
+ return;
+ }
+ DebugLogger.Log("Gateway retrieved: " + CurrentGatewayURL);
+
+ ws = new NetWebSocketWrapper(CurrentGatewayURL);
+
+ ws.MessageReceived += (sender, e) =>
+ {
+ try
+ {
+ var message = JObject.Parse(e.Message);
+
+ 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();
+ }
+ catch (Exception ex)
+ {
+ DebugLogger.Log($"MessageReceived Error: {ex.Message}\n\n```{e.Message}\n```\n", MessageLevel.Error);
+ return;
+ }
+ };
+ 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..");
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void MiscellaneousOpcodes(JObject message)
+ {
+ switch (message["op"].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
+ }
+ });
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private async void ClientPacketReceived(JObject message)
+ {
+ string header = message["t"].ToString();
+
+ Debug.WriteLine(string.Format("[DiscordClient] Received {0} HEADER.", header));
+
+ switch (header)
+ {
+ case "READY":
+ {
+ Sequence = message["s"].ToObject();
+ DiscordGatewayVersion = message["d"]["v"].ToObject();
+ if (message["d"]?["heartbeat_interval"] != null)
+ 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
+ }
+ });
+ await ws.SendMessage(wsChunkTest);
+
+ //ws.Send(@"{""op"":4,""d"":{ ""guild_id"":null,""channel_id"":null,""self_mute"":true,""self_deaf"":false,""self_video"":false}}");
+ }
+
+ ReadyComplete = true;
+
+ await Task.Run(() =>
+ {
+ 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());
+ UserTypingStart?.Invoke(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;
+ case "SESSIONS_REPLACE": // idk
+ default:
+ UnknownMessageTypeReceived?.Invoke(this, new UnknownMessageEventArgs { RawJson = message });
+ break;
+ }
+ }
+
+ private async void SendIdentifyPacket()
+ {
+ string initJson = JsonConvert.SerializeObject(new
+ {
+ op = 2,
+ d = new
+ {
+ v = 4,
+ token = token,
+ /*large_threshold = 50,*/
+ properties = DiscordProperties,
+
+ large_threshold = 100,
+ synced_guilds = DiscordSyncedGuilds,
+ presence = new
+ {
+ status = "online",
+ since = 0,
+ afk = false,
+ game = "null",
+ },
+ compress = false
+ }
+ });
+
+ DebugLogger.Log("Sending initJson ( " + initJson + " )");
+
+ await ws.SendMessage(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)message["d"]["members"];
+
+ Parallel.ForEach(membersAsArray, member =>
+ {
+ string memberId = (string)member["user"]["id"];
+
+ DiscordMember existingMember = inServer.GetMemberByKey((string)member["user"]["id"]);
+
+ if (existingMember == null)
+ {
+ existingMember = JsonConvert.DeserializeObject(member["user"].ToString());
+
+ if (!member["roles"].IsNullOrEmpty())
+ {
+ JArray rollsArray = (JArray)member["roles"];
+ if (rollsArray.Count > 0)
+ {
+ foreach (var rollID in rollsArray)
+ {
+ existingMember.Roles.Add(inServer.Roles.Find(x => x.ID == rollID.ToString()));
+ }
+ }
+ }
+ existingMember.Muted = member["mute"].ToObject();
+ existingMember.Deaf = member["deaf"].ToObject();
+ existingMember.Roles.Add(inServer.Roles.Find(x => x.Name == "@everyone"));
+ existingMember.Status = Status.Offline;
+ existingMember.parentclient = this;
+ existingMember.Parent = inServer;
+ inServer.AddMember(existingMember);
+ }
+ else
+ {
+ // does nothing, already in list
+ }
+
+ ///Check private channels
+ DiscordPrivateChannel _channel = PrivateChannels.Find(x => x.user_id == existingMember.ID);
+ if (_channel != null)
+ {
+ DebugLogger.Log("Found user for private channel!", MessageLevel.Debug);
+ _channel.Recipient = existingMember;
+ }
+ });
+ // Debug.WriteLine("Loaded Member size: " + membersAsArray.Count + " Total size: " + inServer.Members.Count);
+
+ if (membersAsArray.Count != 1000) // chunks of 1000 sent at once.
+ {
+ GuildMembersLoadedEventArgs?.Invoke(this, new DiscordGuildMembersLoadedEventArgs()
+ {
+ Server = inServer,
+ MemberSize = inServer.Members.Count,
+ });
+ }
+ }
+ }
+
+ 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());
+
+ BanRemoved?.Invoke(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)
+ {
+ GuildMemberBanned?.Invoke(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) =>
+ {
+ AudioPacketReceived?.Invoke(sender, e);
+ };
+
+ VoiceClient.DebugMessageReceived += (sender, e) =>
+ {
+ VoiceClientDebugMessageReceived?.Invoke(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 async 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
+ }
+ });
+
+ await ws.SendMessage(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 async 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;
+
+
+ await ws.SendMessage(disconnectMessage);
+ }
+ catch
+ { }
+ }
+ if (ws != null)
+ {
+ await ws.SendMessage(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