500 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
			
		
		
	
	
			500 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
#pragma semicolon 1
 | 
						|
#pragma newdecls required
 | 
						|
#pragma dynamic 65535
 | 
						|
 | 
						|
#include <sourcemod>
 | 
						|
#include <AsyncSocket>
 | 
						|
#include <smjansson>
 | 
						|
 | 
						|
#define MAX_CLIENTS 16
 | 
						|
 | 
						|
#include "API.inc"
 | 
						|
#include "Subscribe.inc"
 | 
						|
 | 
						|
static AsyncSocket g_ServerSocket;
 | 
						|
 | 
						|
static AsyncSocket g_Client_Socket[MAX_CLIENTS] = { null, ... };
 | 
						|
static int g_Client_Subscriber[MAX_CLIENTS] = { -1, ... };
 | 
						|
 | 
						|
ConVar g_ListenAddr;
 | 
						|
ConVar g_ListenPort;
 | 
						|
 | 
						|
public Plugin myinfo =
 | 
						|
{
 | 
						|
	name = "SM JSON API",
 | 
						|
	author = "SourceMod TCP JSON API",
 | 
						|
	description = "",
 | 
						|
	version = "1.0",
 | 
						|
	url = ""
 | 
						|
}
 | 
						|
 | 
						|
public void OnPluginStart()
 | 
						|
{
 | 
						|
	Subscribe_OnPluginStart();
 | 
						|
 | 
						|
	g_ListenAddr = CreateConVar("sm_jsonapi_addr", "127.0.0.1", "SM JSON API listen ip address", FCVAR_PROTECTED);
 | 
						|
	g_ListenPort = CreateConVar("sm_jsonapi_port", "27021", "SM JSON API listen ip address", FCVAR_PROTECTED, true, 1025.0, true, 65535.0);
 | 
						|
 | 
						|
	AutoExecConfig(true, "plugin.SMJSONAPI");
 | 
						|
}
 | 
						|
 | 
						|
public void OnConfigsExecuted()
 | 
						|
{
 | 
						|
	if(g_ServerSocket)
 | 
						|
		return;
 | 
						|
 | 
						|
	g_ServerSocket = new AsyncSocket();
 | 
						|
 | 
						|
	g_ServerSocket.SetConnectCallback(OnAsyncConnect);
 | 
						|
	g_ServerSocket.SetErrorCallback(OnAsyncServerError);
 | 
						|
 | 
						|
	char sAddr[32];
 | 
						|
	g_ListenAddr.GetString(sAddr, sizeof(sAddr));
 | 
						|
	int Port = g_ListenPort.IntValue;
 | 
						|
 | 
						|
	g_ServerSocket.Listen(sAddr, Port);
 | 
						|
	LogMessage("Listening on %s:%d", sAddr, Port);
 | 
						|
}
 | 
						|
 | 
						|
static void OnAsyncConnect(AsyncSocket socket)
 | 
						|
{
 | 
						|
	int Client = GetFreeClientIndex();
 | 
						|
	LogMessage("OnAsyncConnect(Client=%d)", Client);
 | 
						|
 | 
						|
	if(Client == -1)
 | 
						|
	{
 | 
						|
		delete socket;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	g_Client_Socket[Client] = socket;
 | 
						|
	socket.SetErrorCallback(OnAsyncClientError);
 | 
						|
	socket.SetDataCallback(OnAsyncData);
 | 
						|
}
 | 
						|
 | 
						|
static void OnAsyncServerError(AsyncSocket socket, int error, const char[] errorName)
 | 
						|
{
 | 
						|
	SetFailState("OnAsyncServerError(): %d, \"%s\"", error, errorName);
 | 
						|
}
 | 
						|
 | 
						|
static void OnAsyncClientError(AsyncSocket socket, int error, const char[] errorName)
 | 
						|
{
 | 
						|
	int Client = ClientFromSocket(socket);
 | 
						|
	LogMessage("OnAsyncClientError(Client=%d): %d, \"%s\"", Client, error, errorName);
 | 
						|
 | 
						|
	if(Client == -1)
 | 
						|
	{
 | 
						|
		delete socket;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if(g_Client_Subscriber[Client] != -1)
 | 
						|
	{
 | 
						|
		Subscriber_Destroy(g_Client_Subscriber[Client]);
 | 
						|
		g_Client_Subscriber[Client] = -1;
 | 
						|
	}
 | 
						|
 | 
						|
	g_Client_Socket[Client] = null;
 | 
						|
	delete socket;
 | 
						|
}
 | 
						|
 | 
						|
static void OnAsyncData(AsyncSocket socket, const char[] data, const int size)
 | 
						|
{
 | 
						|
	int Client = ClientFromSocket(socket);
 | 
						|
 | 
						|
	int iLine;
 | 
						|
	int iColumn;
 | 
						|
	static char sError[256];
 | 
						|
	JSONValue jRequest = json_load_ex(data, sError, sizeof(sError), iLine, iColumn);
 | 
						|
 | 
						|
	if(jRequest == null)
 | 
						|
	{
 | 
						|
		JSONObject jResponse = new JSONObject();
 | 
						|
 | 
						|
		JSONObject jError = new JSONObject();
 | 
						|
		jError.SetString("type", "json");
 | 
						|
		jError.SetString("error", sError);
 | 
						|
		jError.SetInt("line", iLine);
 | 
						|
		jError.SetInt("column", iColumn);
 | 
						|
 | 
						|
		jResponse.Set("error", jError);
 | 
						|
 | 
						|
		jResponse.ToString(sError, sizeof(sError));
 | 
						|
		socket.WriteNull(sError);
 | 
						|
 | 
						|
		delete jResponse;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if(jRequest.IsObject)
 | 
						|
	{
 | 
						|
		//view_as<JSONObject>(jRequest).DumpToServer();
 | 
						|
		JSONObject jResponse = new JSONObject();
 | 
						|
		// Negative values and objects indicate errors
 | 
						|
		// 0 and positive integers indicate success
 | 
						|
		jResponse.SetInt("error", 0);
 | 
						|
 | 
						|
		HandleRequest(Client, view_as<JSONObject>(jRequest), jResponse);
 | 
						|
 | 
						|
		static char sResponse[4096];
 | 
						|
		jResponse.ToString(sResponse, sizeof(sResponse));
 | 
						|
		socket.WriteNull(sResponse);
 | 
						|
		delete jResponse;
 | 
						|
	}
 | 
						|
 | 
						|
	delete jRequest;
 | 
						|
}
 | 
						|
 | 
						|
static int HandleRequest(int Client, JSONObject jRequest, JSONObject jResponse)
 | 
						|
{
 | 
						|
	int parameter_count_verified = 0;
 | 
						|
	static char sMethod[32];
 | 
						|
	if(jRequest.GetString("method", sMethod, sizeof(sMethod)) < 0)
 | 
						|
	{
 | 
						|
		JSONObject jError = new JSONObject();
 | 
						|
		jError.SetString("error", "Request has no 'method' string-value.");
 | 
						|
		jResponse.Set("error", jError);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	jResponse.SetString("method", sMethod);
 | 
						|
 | 
						|
	if(StrEqual(sMethod, "subscribe") || StrEqual(sMethod, "unsubscribe") || StrEqual(sMethod, "replay"))
 | 
						|
	{
 | 
						|
		int Subscriber = g_Client_Subscriber[Client];
 | 
						|
		if(Subscriber == -1)
 | 
						|
		{
 | 
						|
			Subscriber = Subscriber_Create(Client);
 | 
						|
			if(Subscriber < 0)
 | 
						|
			{
 | 
						|
				JSONObject jError = new JSONObject();
 | 
						|
				jError.SetString("type", "subscribe");
 | 
						|
				jError.SetString("error", "Could not allocate a subscriber.");
 | 
						|
				jError.SetInt("code", Subscriber);
 | 
						|
				jResponse.Set("error", jError);
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			g_Client_Subscriber[Client] = Subscriber;
 | 
						|
		}
 | 
						|
		return Subscriber_HandleRequest(Subscriber, jRequest, jResponse);
 | 
						|
	}
 | 
						|
	else if(StrEqual(sMethod, "function"))
 | 
						|
	{
 | 
						|
		static char sFunction[64];
 | 
						|
		if(jRequest.GetString("function", sFunction, sizeof(sFunction)) < 0)
 | 
						|
		{
 | 
						|
			JSONObject jError = new JSONObject();
 | 
						|
			jError.SetString("error", "Request has no 'function' string-value.");
 | 
						|
			jResponse.Set("error", jError);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		jResponse.SetString("function", sFunction);
 | 
						|
 | 
						|
		static char sAPIFunction[64];
 | 
						|
		Format(sAPIFunction, sizeof(sAPIFunction), "API_%s", sFunction);
 | 
						|
 | 
						|
		Function Fun = GetFunctionByName(INVALID_HANDLE, sAPIFunction);
 | 
						|
		if(Fun == INVALID_FUNCTION)
 | 
						|
		{
 | 
						|
			int Res = Call_StartNative(sFunction);
 | 
						|
			if(!Res)
 | 
						|
			{
 | 
						|
				JSONObject jError = new JSONObject();
 | 
						|
				jError.SetString("error", "Invalid function specified.");
 | 
						|
				jError.SetInt("code", Res);
 | 
						|
				jResponse.Set("error", jError);
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			Call_StartFunction(INVALID_HANDLE, Fun);
 | 
						|
		}
 | 
						|
 | 
						|
		JSONArray jArgsArray = view_as<JSONArray>(jRequest.Get("args"));
 | 
						|
		if(jArgsArray == null || !jArgsArray.IsArray)
 | 
						|
		{
 | 
						|
			delete jArgsArray;
 | 
						|
			Call_Cancel();
 | 
						|
			JSONObject jError = new JSONObject();
 | 
						|
			jError.SetString("error", "Request has no 'args' array-value.");
 | 
						|
			jResponse.Set("error", jError);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		int aiValues[32];
 | 
						|
		int iValues = 0;
 | 
						|
 | 
						|
		float afValues[32];
 | 
						|
		int fValues = 0;
 | 
						|
 | 
						|
		static char asValues[16][1024];
 | 
						|
		int sValues = 0;
 | 
						|
 | 
						|
		for(int i = 0; i < jArgsArray.Length; i++)
 | 
						|
		{
 | 
						|
			bool Fail = false;
 | 
						|
			JSONValue jValue;
 | 
						|
			jValue = jArgsArray.Get(i);
 | 
						|
 | 
						|
			char sType1[32];
 | 
						|
			jValue.TypeToString(sType1, sizeof(sType1));
 | 
						|
 | 
						|
			if(jValue.IsArray)
 | 
						|
			{
 | 
						|
				JSONArray jValueArray = view_as<JSONArray>(jValue);
 | 
						|
				JSONValue jArrayValue = jValueArray.Get(0);
 | 
						|
 | 
						|
				char sType2[32];
 | 
						|
				jArrayValue.TypeToString(sType2, sizeof(sType2));
 | 
						|
 | 
						|
				if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
 | 
						|
				{
 | 
						|
					int iValues_ = iValues;
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
					{
 | 
						|
						JSONInteger jInteger = view_as<JSONInteger>(jValueArray.Get(j));
 | 
						|
						aiValues[iValues_++] = jInteger.Value;
 | 
						|
						delete jInteger;
 | 
						|
					}
 | 
						|
 | 
						|
					Call_PushArrayEx(aiValues[iValues], jValueArray.Length, SM_PARAM_COPYBACK);
 | 
						|
					iValues += jValueArray.Length;
 | 
						|
					parameter_count_verified++;
 | 
						|
				}
 | 
						|
				else if(jArrayValue.IsFloat)
 | 
						|
				{
 | 
						|
					int fValues_ = fValues;
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
					{
 | 
						|
						JSONFloat jFloat = view_as<JSONFloat>(jValueArray.Get(j));
 | 
						|
						afValues[fValues_++] = jFloat.Value;
 | 
						|
						delete jFloat;
 | 
						|
					}
 | 
						|
 | 
						|
					Call_PushArrayEx(afValues[fValues], jValueArray.Length, SM_PARAM_COPYBACK);
 | 
						|
					fValues += jValueArray.Length;
 | 
						|
					parameter_count_verified++;
 | 
						|
				}
 | 
						|
				/*else if(jArrayValue.IsString)
 | 
						|
				{
 | 
						|
					int sValues_ = sValues;
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
					{
 | 
						|
						JSONString jString = view_as<JSONString>(jValueArray.Get(j));
 | 
						|
						jString.GetString(asValues[sValues_++], sizeof(asValues[]));
 | 
						|
						delete jString;
 | 
						|
					}
 | 
						|
 | 
						|
					Call_PushArrayEx(view_as<int>(asValues[sValues]), jValueArray.Length, SM_PARAM_COPYBACK);
 | 
						|
					sValues += jValueArray.Length;
 | 
						|
				}*/
 | 
						|
				else if(jArrayValue.IsArray) // Special
 | 
						|
				{
 | 
						|
					static char sSpecial[32];
 | 
						|
					view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
 | 
						|
 | 
						|
					if(StrEqual(sSpecial, "NULL_VECTOR"))
 | 
						|
					{
 | 
						|
						Call_PushArrayEx(NULL_VECTOR, 3, 0);
 | 
						|
						parameter_count_verified++;
 | 
						|
					}
 | 
						|
					else if(StrEqual(sSpecial, "NULL_STRING"))
 | 
						|
					{
 | 
						|
						Call_PushString(NULL_STRING);
 | 
						|
						parameter_count_verified++;
 | 
						|
					}
 | 
						|
					else
 | 
						|
						Fail = true;
 | 
						|
				}
 | 
						|
				else
 | 
						|
					Fail = true;
 | 
						|
 | 
						|
				if(Fail)
 | 
						|
				{
 | 
						|
					Call_Cancel();
 | 
						|
					delete jArrayValue;
 | 
						|
					delete jValue;
 | 
						|
					delete jArgsArray;
 | 
						|
 | 
						|
					static char sType[16];
 | 
						|
					jValue.TypeToString(sType, sizeof(sType));
 | 
						|
 | 
						|
					char sError[128];
 | 
						|
					FormatEx(sError, sizeof(sError), "Unsupported parameter in list %d of type '%s'", i, sType);
 | 
						|
 | 
						|
					JSONObject jError = new JSONObject();
 | 
						|
					jError.SetString("error", sError);
 | 
						|
					jResponse.Set("error", jError);
 | 
						|
					return -1;
 | 
						|
				}
 | 
						|
				delete jArrayValue;
 | 
						|
			}
 | 
						|
			else if(jValue.IsInteger || jValue.IsBoolean)
 | 
						|
			{
 | 
						|
				aiValues[iValues] = view_as<JSONInteger>(jValue).Value;
 | 
						|
				Call_PushCell(aiValues[iValues++]);
 | 
						|
				parameter_count_verified++;
 | 
						|
			}
 | 
						|
			else if(jValue.IsFloat)
 | 
						|
			{
 | 
						|
				afValues[fValues] = view_as<JSONFloat>(jValue).Value;
 | 
						|
				Call_PushFloat(afValues[fValues++]);
 | 
						|
				parameter_count_verified++;
 | 
						|
			}
 | 
						|
			else if(jValue.IsString)
 | 
						|
			{
 | 
						|
				// Broken: view_as<JSONString>(jValue).GetString(asValues[sValues], sizeof(asValues[]));
 | 
						|
				JSONString jString = view_as<JSONString>(jValue);
 | 
						|
				jString.GetString(asValues[sValues], sizeof(asValues[]));
 | 
						|
				Call_PushStringEx(asValues[sValues++], sizeof(asValues[]), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
 | 
						|
				parameter_count_verified++;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				Call_Cancel();
 | 
						|
				delete jValue;
 | 
						|
				delete jArgsArray;
 | 
						|
 | 
						|
				static char sType[16];
 | 
						|
				jValue.TypeToString(sType, sizeof(sType));
 | 
						|
 | 
						|
				char sError[128];
 | 
						|
				FormatEx(sError, sizeof(sError), "Unsupported parameter %d of type '%s'", i, sType);
 | 
						|
 | 
						|
				JSONObject jError = new JSONObject();
 | 
						|
				jError.SetString("error", sError);
 | 
						|
				jResponse.Set("error", jError);
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			delete jValue;
 | 
						|
		}
 | 
						|
		int sm_call_finish_ex_params_size = 1;
 | 
						|
		if (parameter_count_verified > sm_call_finish_ex_params_size)
 | 
						|
		{
 | 
						|
			Call_Cancel();
 | 
						|
			delete jArgsArray;
 | 
						|
			
 | 
						|
			char sError[128];
 | 
						|
			FormatEx(sError, sizeof(sError), "blocking parameterized call %i", parameter_count_verified);
 | 
						|
			
 | 
						|
			JSONObject jError = new JSONObject();
 | 
						|
			jError.SetString("error", sError);
 | 
						|
			jResponse.Set("error", jError);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		
 | 
						|
		int Result;
 | 
						|
		static char sException[1024];
 | 
						|
		int Error = Call_FinishEx(Result, sException, sizeof(sException));
 | 
						|
 | 
						|
		if(Error != SP_ERROR_NONE)
 | 
						|
		{
 | 
						|
			delete jArgsArray;
 | 
						|
			JSONObject jError = new JSONObject();
 | 
						|
			jError.SetInt("error", Error);
 | 
						|
			jError.SetString("exception", sException);
 | 
						|
			jResponse.Set("error", jError);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		jResponse.SetInt("result", Result);
 | 
						|
 | 
						|
		JSONArray jArgsResponse = new JSONArray();
 | 
						|
		iValues = 0;
 | 
						|
		fValues = 0;
 | 
						|
		sValues = 0;
 | 
						|
 | 
						|
		for(int i = 0; i < jArgsArray.Length; i++)
 | 
						|
		{
 | 
						|
			JSONValue jValue;
 | 
						|
			jValue = jArgsArray.Get(i);
 | 
						|
 | 
						|
			if(jValue.IsArray)
 | 
						|
			{
 | 
						|
				JSONArray jArrayResponse = new JSONArray();
 | 
						|
				JSONArray jValueArray = view_as<JSONArray>(jValue);
 | 
						|
				JSONValue jArrayValue = jValueArray.Get(0);
 | 
						|
 | 
						|
				if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
 | 
						|
				{
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
						jArrayResponse.AppendInt(aiValues[iValues++]);
 | 
						|
				}
 | 
						|
				else if(jArrayValue.IsFloat)
 | 
						|
				{
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
						jArrayResponse.AppendFloat(afValues[fValues++]);
 | 
						|
				}
 | 
						|
				else if(jArrayValue.IsString)
 | 
						|
				{
 | 
						|
					for(int j = 0; j < jValueArray.Length; j++)
 | 
						|
						jArrayResponse.AppendString(asValues[sValues++]);
 | 
						|
				}
 | 
						|
				else if(jArrayValue.IsArray) // Special
 | 
						|
				{
 | 
						|
					static char sSpecial[32];
 | 
						|
					view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
 | 
						|
					jArrayResponse.AppendString(sSpecial);
 | 
						|
				}
 | 
						|
				delete jArrayValue;
 | 
						|
				jArgsResponse.Append(jArrayResponse);
 | 
						|
			}
 | 
						|
			else if(jValue.IsInteger || jValue.IsBoolean)
 | 
						|
			{
 | 
						|
				jArgsResponse.AppendInt(aiValues[iValues++]);
 | 
						|
			}
 | 
						|
			else if(jValue.IsFloat)
 | 
						|
			{
 | 
						|
				jArgsResponse.AppendFloat(afValues[fValues++]);
 | 
						|
			}
 | 
						|
			else if(jValue.IsString)
 | 
						|
			{
 | 
						|
				jArgsResponse.AppendString(asValues[sValues++]);
 | 
						|
			}
 | 
						|
			delete jValue;
 | 
						|
		}
 | 
						|
 | 
						|
		jResponse.Set("args", jArgsResponse);
 | 
						|
 | 
						|
		delete jArgsArray;
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	JSONObject jError = new JSONObject();
 | 
						|
	jError.SetString("error", "No handler found for requested method.");
 | 
						|
	jResponse.Set("error", jError);
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
int PublishEvent(int Client, JSONObject Object)
 | 
						|
{
 | 
						|
	if(Client < 0 || Client > MAX_CLIENTS || g_Client_Socket[Client] == null)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	static char sEvent[4096];
 | 
						|
	Object.ToString(sEvent, sizeof(sEvent));
 | 
						|
	g_Client_Socket[Client].WriteNull(sEvent);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int GetFreeClientIndex()
 | 
						|
{
 | 
						|
	for(int i = 0; i < MAX_CLIENTS; i++)
 | 
						|
	{
 | 
						|
		if(!g_Client_Socket[i])
 | 
						|
			return i;
 | 
						|
	}
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
static int ClientFromSocket(AsyncSocket socket)
 | 
						|
{
 | 
						|
	for(int i = 0; i < MAX_CLIENTS; i++)
 | 
						|
	{
 | 
						|
		if(g_Client_Socket[i] == socket)
 | 
						|
			return i;
 | 
						|
	}
 | 
						|
	return -1;
 | 
						|
}
 |