Skip to content

Instantly share code, notes, and snippets.

@JBou
Last active August 29, 2015 14:03
Show Gist options
  • Save JBou/7fcfa3439d455b930627 to your computer and use it in GitHub Desktop.
Save JBou/7fcfa3439d455b930627 to your computer and use it in GitHub Desktop.
JSONAPI SDK updated to API2
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net;
using System.Threading.Tasks;
namespace tman0.JsonAPI2
{
public class JSONAPI
{
public string Host { get; private set; }
public int Port { get; private set; }
public string Username { get; set; }
public string Password { get; set; }
public string Salt { get; private set; }
public bool Connected { get; private set; }
public event EventHandler<StreamDataReceivedEventArgs> StreamDataReceived;
private bool doInitialize;
private Thread netReadThread;
private Dictionary<long, dynamic> data = new Dictionary<long, dynamic>();
private List<long> streams = new List<long>();
private TcpClient serverConnection = new TcpClient();
private StreamWriter netOut;
private StreamReader netIn;
public JSONAPI(string host, int port, string username, string password, string salt = "")
{
Host = host;
Port = port;
Username = username;
Password = password;
Salt = salt;
Connected = false;
}
public JSONAPI(string host, int port)
{
Host = host;
Port = port;
Connected = false;
}
/// <summary>
/// Connect to the server.
/// </summary>
/// <exception cref="SocketException">An error occurred connecting to the server.</exception>
public void Connect()
{
serverConnection.Connect(Host, Port + 1);
netOut = new StreamWriter(serverConnection.GetStream());
netIn = new StreamReader(serverConnection.GetStream());
netOut.AutoFlush = true;
// start the inbound data reader stream
netReadThread = new Thread(readThread);
netReadThread.Start();
Connected = true;
}
/// <summary>
/// Connect to the specified server.
/// </summary>
/// <param name="host">The hostname of the server to connect to.</param>
/// <param name="port">The port of the server to connect to.</param>
public void Connect(string host, int port)
{
serverConnection.Connect(host, port + 1);
netOut = new StreamWriter(serverConnection.GetStream());
netIn = new StreamReader(serverConnection.GetStream());
netOut.AutoFlush = true;
Host = host;
Port = port;
// start the inbound data reader stream
netReadThread = new Thread(readThread);
netReadThread.Start();
Connected = true;
}
/// <summary>
/// Disconnect from the server.
/// </summary>
/// <param name="host">The hostname of the server to connect to.</param>
/// <param name="port">The port of the server to connect to.</param>
public void Disconnect()
{
//close the TCP Connection
serverConnection.Close();
// suspend the inbound data reader stream
netReadThread.Suspend();
Connected = false;
}
/// <summary>
/// Generate a key for use with Call().
/// </summary>
/// <param name="method">The method to call.</param>
/// <param name="username">The server's username.</param>
/// <param name="password">The server's password.</param>
/// <param name="salt">The server's salt.</param>
/// <returns></returns>
public string MakeKey(string method, string username, string password, string salt = "")
{
string key = BitConverter.ToString(
SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(username + method + password + salt)))
.Replace("-", "").ToLower();
return key;
}
/// <summary>
/// Call a method on the server. [Thread Safe]
/// </summary>
/// <param name="method">The method to call.</param>
/// <param name="key">The key to use. A key will be generated if left null.</param>
/// <param name="args">Arguments to pass to the server.</param>
/// <returns>A string if there was an error, otherwise the object returned by the server.</returns>
public dynamic Call(string method, string key = null, params object[] args)
{
if (!Connected) throw new IOException("Connect to the server first! (Use JsonAPI.Connect())");
if (key == null) key = MakeKey(method, Username, Password, Salt); // create a key if there's not already one
var w = new StringWriter();
new JsonSerializer().Serialize(w, args);
var now = DateTime.Now.Ticks; // get the current time in ticks so we can refer to it later
var requestUrl = "/api/call?method=" + method + "&args=" + Uri.EscapeDataString(w.ToString()) + "&key=" + key + "&tag=" + now; // build the request URL
data.Add(now, null); // let the reader thread know that we're expecting data with this tag
netOut.WriteLine(requestUrl); // send the request
while (data[now] == null) ; // hope for the best
var toReturn = data[now];
data.Remove(now);
return toReturn; // return the rest
}
/// <summary>
/// Call a method on the server.
/// </summary>
/// <param name="method">The method to call.</param>
/// <param name="tag">The tag that will be returned in the response.</param>
/// <param name="args">Arguments to pass to the server.</param>
/// <returns>Returns a Response with the data received form the server</returns>
public async Task<APIResponse> Callv2(string method, params object[] args)
{
if (!Connected) throw new IOException("Connect to the server first! (Use JsonAPI.Connect())");
string Key = MakeKey(method, Username, Password, Salt);
var now = DateTime.Now.Ticks;
JArray json = new JArray(
new JObject(
new JProperty("name", method),
new JProperty("key", Key),
new JProperty("username", Username),
new JProperty("arguments", new JArray(args)),
new JProperty("tag", now)
));
string requestUrl = "/api/2/call?json=" + json.ToString(Formatting.None);
Uri url = new Uri("http://" + Host + ":" + Port + requestUrl);
string dl = await new WebClient().DownloadStringTaskAsync(url);
JArray ja = JArray.Parse(dl);
APIResponse toReturn = await JsonConvert.DeserializeObjectAsync<APIResponse>(ja[0].ToString());
return toReturn;
//data.Add(now, null); // let the reader thread know that we're expecting data with this tag
//netOut.WriteLine(requestUrl); // send the request
//while (data[now] == null) ; // hope for the best
////var toReturn = data[now];
//Response toReturn = JsonConvert.DeserializeObjectAsync<Response>(data[now]);
//data.Remove(now);
//return toReturn;
}
/// <summary>
/// Call multiple methods on the server.
/// </summary>
/// <param name="methods">A string[] of methods to call on the server.</param>
/// <param name="key">The key to use for this request. A key will be generated if left null.</param>
/// <param name="args">Arguments to pass to the server.</param>
/// <returns>A dictionary containing the results of the method calls.</returns>
public Dictionary<string, dynamic> CallMultiple(string[] methods, string key = null, params object[][] args)
{
if (!Connected) throw new IOException("Connect to the server first! (Use JsonAPI.Connect())");
if (key == null) key = MakeKey(JsonConvert.SerializeObject(methods), Username, Password, Salt); // create a key if there's not already one
var w = new StringWriter();
new JsonSerializer().Serialize(w, args);
var now = DateTime.Now.Ticks; // get the current time in ticks so we can refer to it later
var requestUrl = string.Format("/api/call-multiple?method={0}&args={1}&key={2}&tag={3}",
Uri.EscapeDataString(JsonConvert.SerializeObject(methods)), Uri.EscapeDataString(w.ToString()), key, now); // build the request URL
data.Add(now, null); // let the reader thread know that we're expecting data with this tag
netOut.WriteLine(requestUrl); // send the request
while (data[now] == null) ; // hope for the best
var toReturn = new Dictionary<string, dynamic>();
foreach (JObject o in data[now])
{
toReturn.Add((string)o["source"], o["success"]);
}
data.Remove(now);
return toReturn; // return the rest
}
/// <summary>
/// Subscribe to a stream source, which can be viewed with the StreamDataReceived event handler.
/// </summary>
/// <param name="source">The source to subscribe to. Generally "console", "chat", or "connections".</param>
/// <param name="key">The key to use to subscribe. A key will be generated if left null.</param>
/// <param name="sendPrevious">Whether or not to send the previous 50 items along with the most recent. These will be sent as any other stream message through the StreamDataReceived event.</param>
public void Subscribe(string source, string key = null, bool sendPrevious = false)
{
if (!Connected) throw new IOException("Connect to the server first! (Use JsonAPI.Connect())");
if (key == null) key = MakeKey(source, Username, Password, Salt); // create a key if there's not already one
var now = DateTime.Now.Ticks;
var requestUrl = string.Format("/api/subscribe?source={0}&key={1}&show_previous={2}&tag={3}", source, key,
sendPrevious, now);
streams.Add(now);
netOut.WriteLine(requestUrl);
}
/// <summary>
/// Subscribe to a stream source, which can be viewed with the StreamDataReceived event handler.
/// </summary>
/// <param name="source">The source to subscribe to. Generally "console", "chat", or "connections".</param>
/// <param name="sendPrevious">Whether or not to send the previous 50 items along with the most recent. These will be sent as any other stream message through the StreamDataReceived event.</param>
public void Subscribev2(string source, bool sendPrevious = false)
{
if (!Connected) throw new IOException("Connect to the server first! (Use JsonAPI.Connect())");
string key = MakeKey(source, Username, Password, Salt); // create a key
var now = DateTime.Now.Ticks;
JArray json = new JArray(new JObject(
new JProperty("name", source),
new JProperty("key", key),
new JProperty("username", Username),
new JProperty("tag", now),
new JProperty("show_previous", sendPrevious)
));
var requestUrl = "/api/2/subscribe?json=" + json.ToString(Formatting.None);
streams.Add(now);
netOut.WriteLine(requestUrl);
}
/// <summary>
/// The thread which reads and processes network data.
/// </summary>
private void readThread()
{
while (!serverConnection.Connected) ; // wait for a connection
while (serverConnection.Connected)
{
var line = netIn.ReadLine(); // wait for data
var response = JsonConvert.DeserializeObject<dynamic>(line); // deserialize the data we get into a dynamic object
if (streams.Contains((long)response.tag))
{
if (response.result == "error") // do more fancy error checking later, an exception here would be nice
StreamDataReceived(this, new StreamDataReceivedEventArgs(true, (string)response.source, response.error));
else
StreamDataReceived(this, new StreamDataReceivedEventArgs(false, (string)response.source, response.success));
}
else
{
if (response.result == "error") // do more fancy error checking later, an exception here would be nice
data[(long)response.tag] = response.error;
else
data[(long)response.tag] = response.success;
}
#if JSONAPI_DEBUG
#endif
}
// We must have disconnected somehow...
Connected = false;
}
}
public class StreamDataReceivedEventArgs : EventArgs
{
public StreamDataReceivedEventArgs(bool error, string source, dynamic data)
{
Error = error;
Data = data;
Source = source;
}
/// <summary>
/// Whether or not the stream data was the result of an error.
/// </summary>
public bool Error { get; private set; }
/// <summary>
/// The data returned with the stream message. If Error is true, then it is the error message.
/// </summary>
public dynamic Data { get; private set; }
/// <summary>
/// The source which the stream message came from.
/// </summary>
public string Source { get; private set; }
}
}
public class APIResponseError
{
public APIResponseError() {}
public string message { get; set; }
public int? code { get; set; }
}
public class APIResponse
{
public APIResponse() {}
public string result { get; set; }
public bool is_success { get; set; }
public object success { get; set; }
public APIResponseError error { get; set; }
public string source { get; set; }
public string tag { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment