From ce726e955590ea72014d125aaca9b00e5fe5c3dd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 2 May 2018 12:59:06 -0400 Subject: [PATCH] Done prototyping the v4 DMAPI --- src/DMAPI/tgs.dm | 6 +- src/DMAPI/tgs/includes.dm | 1 + src/DMAPI/tgs/v4/api.dm | 166 ++++++++++++++++++++++++++--------- src/DMAPI/tgs/v4/commands.dm | 72 +++++++++++++++ tgstation-server.sln | 1 + 5 files changed, 203 insertions(+), 43 deletions(-) create mode 100644 src/DMAPI/tgs/v4/commands.dm diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index e885b06397..e68bfa9b61 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -44,6 +44,8 @@ //EVENT CODES +#define TGS_EVENT_PORT_SWAP 1 //before a port change is about to happen, extra parameter is new port + //TODO //REQUIRED HOOKS @@ -101,7 +103,9 @@ var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from //user definable callback for handling events -/datum/tgs_event_handler/proc/HandleEvent(event_code) +//extra parameters may be specified depending on the event +/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) + set waitfor = FALSE return //user definable chat command diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm index b0ecec7f8e..2fdd5ed1e0 100644 --- a/src/DMAPI/tgs/includes.dm +++ b/src/DMAPI/tgs/includes.dm @@ -5,3 +5,4 @@ #include "v3210\api.dm" #include "v3210\commands.dm" #include "v4\api.dm" +#include "v4\commands.dm" diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 8ded8ee40a..685d691d21 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -1,17 +1,18 @@ -#define TGS4_API_VALIDATE "tgs_vali" -#define TGS4_PARAM_TOKEN "tgs_tok" -#define TGS4_PARAM_INSTANCE "tgs_inst_name" -#define TGS4_PARAM_INSTANCE_ID "tgs_inst_id" -#define TGS4_PARAM_REVISION_JSON "tgs_json" -#define TGS4_PARAM_HOST_PORT "tgs_port" -#define TGS4_PARAM_SECOND_PORT "tgs_port2" +#define TGS4_PARAM_INFO_JSON "tgs_json" #define TGS4_TOPIC_COMMAND "tgs_com" +#define TGS4_TOPIC_TOKEN "tgs_tok" +#define TGS4_TOPIC_SUCCESS "tgs_success" +#define TGS4_TOPIC_SWAP "tgs_swap" +#define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed" +#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" +#define TGS4_TOPIC_EVENT "tgs_event" +#define TGS4_COMM_VALIDATE "tgs_vali" #define TGS4_COMM_SERVER_PRIMED "tgs_prime" +#define TGS4_COMM_SERVER_REBOOT "tgs_reboot" #define TGS4_COMM_END_PROCESS "tgs_kill" -#define TGS4_COMM_CHAT_BROADCAST "tgs_chat" -#define TGS4_COMM_CHAT_PM "tgs_chat_pm" +#define TGS4_COMM_CHAT "tgs_chat_send" /datum/tgs_api/v4 var/access_token @@ -20,33 +21,57 @@ var/host_port var/port_1 var/port_2 - var/revision_json_path + var/cached_json + var/json_path + + var/list/custom_commands + + var/list/cached_test_merges + var/datum/tgs_revision_information/cached_revision + + var/datum/tgs_event_handler/event_handler /datum/tgs_api/v4/ApiVersion() return "4.0.0.0" /datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler) - access_token = world.params[TGS4_PARAM_TOKEN] - instance_id = world.params[TGS4_PARAM_INSTANCE_ID] - host_port = world.params[TGS4_PARAM_HOST_PORT] - if(world.params[TGS4_API_VALIDATE]) - Export(TGS4_API_VALIDATE) + json_path = world.params[TGS4_PARAM_INFO_JSON] + if(!json_path) + TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!") + return + var/json_file = file2text(json_path) + if(!json_file) + TGS_ERROR_LOG("Missing specified json file: [json_path]") + return + cached_json = json_decode(json_file) + if(!cached_json) + TGS_ERROR_LOG("Failed to decode info json: [json_file]") + return + + access_token = cached_json["access_token"] + instance_id = text2num(cached_json["instance_id"]) + host_port = text2num(cached_json["host_port"]) + if(cached_json["api_validate_only"]) + Export(TGS4_COMM_VALIDATE) del(world) - instance_name = world.params[TGS4_PARAM_INSTANCE] + src.event_handler = event_handler + instance_name = cached_json["instance_name"] port_1 = world.port - port_2 = world.params[TGS4_PARAM_SECOND_PORT] - revision_json_path = world.params[TGS4_PARAM_REVISION_JSON] + port_2 = cached_json["next_port"] + ListCustomCommands() /datum/tgs_api/v4/OnInitializationComplete() Export(TGS4_COMM_SERVER_PRIMED) + var/tgs4_secret_sleep_offline_sauce = 24051994 var/old_sleep_offline = world.sleep_offline sleep(1) - world.sleep_offline = old_sleep_offline + if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it + world.sleep_offline = old_sleep_offline /datum/tgs_api/v4/OnTopic(T) var/list/params = params2list(T) - var/their_sCK = params[TGS4_PARAM_TOKEN] + var/their_sCK = params[TGS4_TOPIC_TOKEN] if(!their_sCK) return FALSE //continue world/Topic @@ -58,27 +83,48 @@ return "No command!" switch(command) + if(TGS4_TOPIC_SWAP) + SwapPorts(FALSE) + return TGS4_TOPIC_SUCCESS + if(TGS4_TOPIC_SWAP_DELAYED) + SwapPorts(TRUE) + return TGS4_TOPIC_SUCCESS + if(TGS4_TOPIC_CHAT_COMMAND) + var/result = HandleCustomCommand(params[TGS4_TOPIC_CHAT_COMMAND]) + if(!result) + return json_encode(list("error" = "Error running chat command!")) + return result + if(TGS4_TOPIC_EVENT) + event_handler.HandleEvent(text2num(params[TGS4_TOPIC_EVENT])) + return TGS4_TOPIC_SUCCESS return "Unknown command: [command]" +/datum/tgs_api/v4/proc/SwapPorts(delayed) + set waitfor = FALSE + var/new_port = world.port == port_1 ? port_2 : port_1 + event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) + world.OpenPort("none") //close the port + if(delayed) + sleep(50) //wait for other server to close port + world.OpenPort(new_port) + /datum/tgs_api/v4/proc/Export(command) - return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[command]&access_token=[access_token]") + return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") /datum/tgs_api/v4/OnReboot() - return TGS_UNIMPLEMENTED + Export(TGS4_COMM_SERVER_REBOOT) /datum/tgs_api/v4/InstanceName() return instance_name /datum/tgs_api/v4/TestMerges() - //do the best we can here as the datum can't be completed using the v3 api + if(cached_test_merges) + return cached_test_merges + . = list() - if(!revision_json_path || !fexists(revision_json_path)) - return - var/list/json = json_decode(file2text(revision_json_path)) - if(!json) - return - json = json["test_merges"] + cached_test_merges = . + var/json = cached_json["test_merges"] for(var/I in json) var/datum/tgs_revision_information/test_merge/tm = new tm.number = text2num(I) @@ -87,7 +133,7 @@ tm.author = entry["author"] tm.title = entry["title"] tm.commit = entry["commit"] - tm.commit = entry["origin_commit"] + tm.origin_commit = entry["origin_commit"] tm.time_merged = text2num(entry["time_merged"]) tm.comment = entry["comment"] tm.url = entry["url"] @@ -97,24 +143,60 @@ Export(TGS4_COMM_END_PROCESS) /datum/tgs_api/v4/Revision() - if(!revision_json_path || !fexists(revision_json_path)) - return - var/list/json = json_decode(file2text(revision_json_path)) - if(!json) - return - var/datum/tgs_revision_information - -/datum/tgs_api/v4/ChatChannelInfo() - return TGS_UNIMPLEMENTED + if(!cached_revision) + var/json = cached_json["revision"] + cached_revision = new + cached_revision.commit = json["commit"] + cached_revision.origin_commit = json["origin_commit"] + return cached_revision /datum/tgs_api/v4/ChatBroadcast(message, list/channels) - return TGS_UNIMPLEMENTED + var/list/ids + if(length(channels)) + ids = list() + for(var/I in channels) + var/datum/tgs_chat_channel/channel = I + ids += channel.id + message = list("message" = message, "channels" = ids) + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) - return TGS_UNIMPLEMENTED + message = list("message" = message, "channels" = admin_only ? "admin" : "game") + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) - Export("[TGS_COMM_CHAT_PM] [user.id] [user.channel.id]") + message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id)) + Export("[TGS4_COMM_CHAT] [json_encode(message)]") + +/datum/tgs_api/v4/ChatChannelInfo() + . = list() + //no caching cause tgs may change this + var/chat_json_path = cached_json["chat_channels_json"] + var/list/json = json_decode(file2text(chat_json_path)) + for(var/I in json) + var/datum/tgs_chat_channel/channel = new + channel.id = I["id"] + channel.friendly_name = I["friendly_name"] + channel.server_name = I["server_name"] + channel.provider_name = I["provider_name"] + channel.is_admin_channel = I["is_admin_channel"] + channel.is_private_channel = FALSE //tgs will never send us pm channels + . += channel + +#undef TGS4_TOPIC_COMMAND +#undef TGS4_TOPIC_TOKEN +#undef TGS4_TOPIC_SUCCESS +#undef TGS4_TOPIC_SWAP +#undef TGS4_TOPIC_SWAP_DELAYED +#undef TGS4_TOPIC_CHAT_COMMAND +#undef TGS4_TOPIC_EVENT + +#undef TGS4_COMM_SERVER_PRIMED +#undef TGS4_COMM_SERVER_REBOOT +#undef TGS4_COMM_END_PROCESS +#undef TGS4_COMM_CHAT + +#undef TGS4_COMM_VALIDATE /* The MIT License diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm new file mode 100644 index 0000000000..bf32f360e3 --- /dev/null +++ b/src/DMAPI/tgs/v4/commands.dm @@ -0,0 +1,72 @@ +/datum/tgs_api/v4/proc/ListCustomCommands() + var/results = list() + custom_commands = list() + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + var/datum/tgs_chat_command/stc = new I + var/command_name = stc.name + if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) + TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") + continue + + if(results[command_name]) + var/datum/other = custom_commands[command_name] + TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") + continue + results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) + custom_commands[command_name] = stc + + var/commands_file = cached_json["chat_commands_file"] + if(!commands_file) + return + text2file(json_encode(results), commands_file) + +/datum/tgs_api/v4/proc/HandleCustomCommand(command_json) + var/list/data = json_decode(command_json) + var/command = data["command"] + var/user = data["user"] + var/params = data["params"] + + var/datum/tgs_chat_user/u = new + u.id = user["id"] + u.friendly_name = user["friendly_name"] + u.mention = user["mention"] + var/datum/tgs_chat_channel/channel = new + u.channel = channel + var/channel_json = user["channel"] + channel.id = channel_json["id"] + channel.friendly_name = channel_json["friendly_name"] + channel.server_name = channel_json["server_name"] + channel.is_admin_channel = channel_json["is_admin_channel"] + channel.is_private_channel = channel_json["is_private_channel"] + + var/datum/tgs_chat_command/sc = custom_commands[command] + var/result = sc.Run(u, params) + return json_encode(list("result" = result)) + +/* + +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/tgstation-server.sln b/tgstation-server.sln index 63e83585fe..7ceda39bca 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -96,6 +96,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31-4948-91C6-B0977C335890}" ProjectSection(SolutionItems) = preProject src\DMAPI\tgs\v4\api.dm = src\DMAPI\tgs\v4\api.dm + src\DMAPI\tgs\v4\commands.dm = src\DMAPI\tgs\v4\commands.dm EndProjectSection EndProject Global