Retrofitting new DMAPI libs.

This commit is contained in:
Aeris Bennet
2024-02-06 20:05:46 +00:00
committed by GitHub
parent 91f4eea62c
commit ef92a397ef
19 changed files with 2041 additions and 552 deletions
+10 -2
View File
@@ -1,2 +1,10 @@
#define TGS_UNIMPLEMENTED "___unimplemented"
#define TGS_VERSION_PARAMETER "server_service_version"
#if DM_VERSION < 510
#error The TGS DMAPI does not support BYOND versions < 510!
#endif
#define TGS_UNIMPLEMENTED "___unimplemented"
#define TGS_VERSION_PARAMETER "server_service_version"
#ifndef TGS_DEBUG_LOG
#define TGS_DEBUG_LOG(message)
#endif
+168 -144
View File
@@ -1,144 +1,168 @@
/world/TgsNew(datum/tgs_event_handler/event_handler)
var/tgs_version = world.params[TGS_VERSION_PARAMETER]
if(!tgs_version)
return
var/path = SelectTgsApi(tgs_version)
if(!path)
TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.")
TGS_INFO_LOG("Activating API for version [tgs_version]")
var/datum/tgs_api/new_api = new path
var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default)
if(result && result != TGS_UNIMPLEMENTED)
TGS_WRITE_GLOBAL(tgs, new_api)
else
TGS_ERROR_LOG("Failed to activate API!")
/world/proc/SelectTgsApi(tgs_version)
//remove the old 3.0 header
tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "")
var/list/version_bits = splittext(tgs_version, ".")
var/super = text2num(version_bits[1])
var/major = text2num(version_bits[2])
var/minor = text2num(version_bits[3])
var/patch = text2num(version_bits[4])
switch(super)
if(3)
switch(major)
if(2)
return /datum/tgs_api/v3210
if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion())
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
return /datum/tgs_api/latest
/world/TgsMaximumAPIVersion()
return "4.0.0.0"
/world/TgsMinimumAPIVersion()
return "3.2.0.0"
/world/TgsInitializationComplete()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.OnInitializationComplete()
/world/proc/TgsTopic(T)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.OnTopic(T)
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsRevision()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.Revision()
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsReboot()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.OnReboot()
/world/TgsAvailable()
return TGS_READ_GLOBAL(tgs) != null
/world/TgsVersion()
return world.params[TGS_VERSION_PARAMETER]
/world/TgsInstanceName()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.InstanceName()
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsTestMerges()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.TestMerges()
if(result != TGS_UNIMPLEMENTED)
return result
return list()
/world/TgsEndProcess()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.EndProcess()
/world/TgsChatChannelInfo()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.ChatChannelInfo()
if(result != TGS_UNIMPLEMENTED)
return result
return list()
/world/TgsChatBroadcast(message, list/channels)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatBroadcast(message, channels)
/world/TgsTargetedChatBroadcast(message, admin_only)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatTargetedBroadcast(message, admin_only)
/world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatPrivateMessage(message, user)
/*
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.
*/
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
var/current_api = TGS_READ_GLOBAL(tgs)
if(current_api)
TGS_ERROR_LOG("API datum already set (\ref[current_api] ([current_api]))! Was TgsNew() called more than once?")
return
if(!(minimum_required_security_level in list(TGS_SECURITY_ULTRASAFE, TGS_SECURITY_SAFE, TGS_SECURITY_TRUSTED)))
TGS_ERROR_LOG("Invalid minimum_required_security_level: [minimum_required_security_level]!")
return
#ifdef TGS_V3_API
if(minimum_required_security_level != TGS_SECURITY_TRUSTED)
TGS_WARNING_LOG("V3 DMAPI requires trusted security!")
minimum_required_security_level = TGS_SECURITY_TRUSTED
#endif
var/raw_parameter = world.params[TGS_VERSION_PARAMETER]
if(!raw_parameter)
return
var/datum/tgs_version/version = new(raw_parameter)
if(!version.Valid(FALSE))
TGS_ERROR_LOG("Failed to validate DMAPI version parameter: [raw_parameter]!")
return
var/api_datum
switch(version.suite)
if(3)
#ifndef TGS_V3_API
TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!")
return
#else
switch(version.minor)
if(2)
api_datum = /datum/tgs_api/v3210
#endif
if(4)
switch(version.minor)
if(0)
api_datum = /datum/tgs_api/v4
if(5)
api_datum = /datum/tgs_api/v5
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
if(!api_datum)
TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
return
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
if(event_handler && !istype(event_handler))
TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]")
event_handler = null
var/datum/tgs_api/new_api = new api_datum(event_handler, version)
TGS_WRITE_GLOBAL(tgs, new_api)
var/result = new_api.OnWorldNew(minimum_required_security_level)
if(!result || result == TGS_UNIMPLEMENTED)
TGS_WRITE_GLOBAL(tgs, null)
TGS_ERROR_LOG("Failed to activate API!")
/world/TgsMaximumApiVersion()
return new /datum/tgs_version("5.x.x")
/world/TgsMinimumApiVersion()
return new /datum/tgs_version("3.2.x")
/world/TgsInitializationComplete()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.OnInitializationComplete()
/world/proc/TgsTopic(T)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.OnTopic(T)
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsRevision()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.Revision()
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsReboot()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.OnReboot()
/world/TgsAvailable()
return TGS_READ_GLOBAL(tgs) != null
/world/TgsVersion()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.version
/world/TgsApiVersion()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.ApiVersion()
/world/TgsEngine()
#ifdef OPENDREAM
return TGS_ENGINE_TYPE_OPENDREAM
#else
return TGS_ENGINE_TYPE_BYOND
#endif
/world/TgsInstanceName()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.InstanceName()
if(result != TGS_UNIMPLEMENTED)
return result
/world/TgsTestMerges()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.TestMerges()
if(result != TGS_UNIMPLEMENTED)
return result
return list()
/world/TgsEndProcess()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.EndProcess()
/world/TgsChatChannelInfo()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
var/result = api.ChatChannelInfo()
if(result != TGS_UNIMPLEMENTED)
return result
return list()
/world/TgsChatBroadcast(message, list/channels)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatBroadcast(message, channels)
/world/TgsTargetedChatBroadcast(message, admin_only)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatTargetedBroadcast(message, admin_only)
/world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
api.ChatPrivateMessage(message, user)
/world/TgsSecurityLevel()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.SecurityLevel()
/world/TgsVisibility()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.Visibility()
+71 -74
View File
@@ -1,74 +1,71 @@
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
/datum/tgs_api
/datum/tgs_api/latest
parent_type = /datum/tgs_api/v3210
TGS_PROTECT_DATUM(/datum/tgs_api)
/datum/tgs_api/proc/ApiVersion()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnInitializationComplete()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnTopic(T)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnReboot()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/InstanceName()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/TestMerges()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/EndProcess()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/Revision()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatChannelInfo()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatBroadcast(message, list/channels)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only)
return TGS_UNIMPLEMENTED
/*
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.
*/
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
/datum/tgs_api
var/datum/tgs_version/version
var/datum/tgs_event_handler/event_handler
var/list/warned_deprecated_command_runs
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version)
. = ..()
src.event_handler = event_handler
src.version = version
/datum/tgs_api/proc/TerminateWorld()
while(TRUE)
TGS_DEBUG_LOG("About to terminate world. Tick: [world.time], sleep_offline: [world.sleep_offline]")
world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866
del(world)
world.sleep_offline = FALSE // just in case, this is BYOND after all...
sleep(1)
TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]")
/datum/tgs_api/latest
parent_type = /datum/tgs_api/v5
TGS_PROTECT_DATUM(/datum/tgs_api)
/datum/tgs_api/proc/ApiVersion()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnInitializationComplete()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnTopic(T)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/OnReboot()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/InstanceName()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/TestMerges()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/EndProcess()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/Revision()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatChannelInfo()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatBroadcast(message, list/channels)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/ChatPrivateMessage(message, datum/tgs_chat_user/user)
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/SecurityLevel()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/Visibility()
return TGS_UNIMPLEMENTED
+28
View File
@@ -0,0 +1,28 @@
/datum/tgs_version/New(raw_parameter)
src.raw_parameter = raw_parameter
deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "")
var/list/version_bits = splittext(deprefixed_parameter, ".")
suite = text2num(version_bits[1])
if(version_bits.len > 1)
minor = text2num(version_bits[2])
if(version_bits.len > 2)
patch = text2num(version_bits[3])
if(version_bits.len == 4)
deprecated_patch = text2num(version_bits[4])
/datum/tgs_version/proc/Valid(allow_wildcards = FALSE)
if(suite == null)
return FALSE
if(allow_wildcards)
return TRUE
return !Wildcard()
/datum/tgs_version/Wildcard()
return minor == null || patch == null
/datum/tgs_version/Equals(datum/tgs_version/other_version)
if(!istype(other_version))
return FALSE
return suite == other_version.suite && minor == other_version.minor && patch == other_version.patch && deprecated_patch == other_version.deprecated_patch
+21 -6
View File
@@ -1,6 +1,21 @@
#include "core\_definitions.dm"
#include "core\core.dm"
#include "core\datum.dm"
#include "core\default_event_handler.dm"
#include "v3210\api.dm"
#include "v3210\commands.dm"
#include "core\_definitions.dm"
#include "core\core.dm"
#include "core\datum.dm"
#include "core\tgs_version.dm"
#ifdef TGS_V3_API
#include "v3210\api.dm"
#include "v3210\commands.dm"
#endif
#include "v4\api.dm"
#include "v4\commands.dm"
#include "v5\_defines.dm"
#include "v5\api.dm"
#include "v5\bridge.dm"
#include "v5\chunking.dm"
#include "v5\commands.dm"
#include "v5\serializers.dm"
#include "v5\topic.dm"
#include "v5\undefs.dm"
+6
View File
@@ -0,0 +1,6 @@
# DMAPI V3
This DMAPI implements bridge using file output which TGS monitors for.
- [api.dm](./api.dm) contains the bulk of the API code.
- [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
+244 -248
View File
@@ -1,248 +1,244 @@
#define REBOOT_MODE_NORMAL 0
#define REBOOT_MODE_HARD 1
#define REBOOT_MODE_SHUTDOWN 2
#define SERVICE_WORLD_PARAM "server_service"
#define SERVICE_INSTANCE_PARAM "server_instance"
#define SERVICE_PR_TEST_JSON "prtestjob.json"
#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll"
#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint"
#define SERVICE_CMD_HARD_REBOOT "hard_reboot"
#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown"
#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce"
#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands"
#define SERVICE_CMD_API_COMPATIBLE "api_compat"
#define SERVICE_CMD_PLAYER_COUNT "client_count"
#define SERVICE_CMD_PARAM_KEY "serviceCommsKey"
#define SERVICE_CMD_PARAM_COMMAND "command"
#define SERVICE_CMD_PARAM_SENDER "sender"
#define SERVICE_CMD_PARAM_CUSTOM "custom"
#define SERVICE_REQUEST_KILL_PROCESS "killme"
#define SERVICE_REQUEST_IRC_BROADCAST "irc"
#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc"
#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot"
#define SERVICE_REQUEST_API_VERSION "api_ver"
#define SERVICE_RETURN_SUCCESS "SUCCESS"
/datum/tgs_api/v3210
var/reboot_mode = REBOOT_MODE_NORMAL
var/comms_key
var/instance_name
var/originmastercommit
var/commit
var/list/cached_custom_tgs_chat_commands
var/warned_revison = FALSE
var/warned_custom_commands = FALSE
/datum/tgs_api/v3210/ApiVersion()
return "3.2.1.0"
/datum/tgs_api/v3210/proc/trim_left(text)
for (var/i = 1 to length(text))
if (text2ascii(text, i) > 32)
return copytext(text, i)
return ""
/datum/tgs_api/v3210/proc/trim_right(text)
for (var/i = length(text), i > 0, i--)
if (text2ascii(text, i) > 32)
return copytext(text, 1, i + 1)
return ""
/datum/tgs_api/v3210/proc/file2list(filename)
return splittext(trim_left(trim_right(file2text(filename))), "\n")
/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version
. = FALSE
comms_key = world.params[SERVICE_WORLD_PARAM]
instance_name = world.params[SERVICE_INSTANCE_PARAM]
if(!instance_name)
instance_name = "TG Station Server" //maybe just upgraded
var/list/logs = file2list(".git/logs/HEAD")
if(logs.len)
logs = splittext(logs[logs.len - 1], " ")
commit = logs[2]
logs = file2list(".git/logs/refs/remotes/origin/master")
if(logs.len)
originmastercommit = splittext(logs[logs.len - 1], " ")[2]
if(world.system_type != MS_WINDOWS)
TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!")
return
ListServiceCustomCommands(TRUE)
ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE)
return TRUE
//nothing to do for v3
/datum/tgs_api/v3210/OnInitializationComplete()
return
/datum/tgs_api/v3210/InstanceName()
return world.params[SERVICE_INSTANCE_PARAM]
/datum/tgs_api/v3210/proc/ExportService(command, skip_compat_check = FALSE)
. = FALSE
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
return
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
return TRUE
/datum/tgs_api/v3210/OnTopic(T)
var/list/params = params2list(T)
var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
if(!their_sCK)
return FALSE //continue world/Topic
if(their_sCK != comms_key)
return "Invalid comms key!";
var/command = params[SERVICE_CMD_PARAM_COMMAND]
if(!command)
return "No command!"
switch(command)
if(SERVICE_CMD_API_COMPATIBLE)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_HARD_REBOOT)
if(reboot_mode != REBOOT_MODE_HARD)
reboot_mode = REBOOT_MODE_HARD
TGS_INFO_LOG("Hard reboot requested by service")
TGS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by TGS.")
if(SERVICE_CMD_GRACEFUL_SHUTDOWN)
if(reboot_mode != REBOOT_MODE_SHUTDOWN)
reboot_mode = REBOOT_MODE_SHUTDOWN
TGS_INFO_LOG("Shutdown requested by service")
TGS_NOTIFY_ADMINS("The world will shutdown at the end of the game. Requested by TGS.")
if(SERVICE_CMD_WORLD_ANNOUNCE)
var/msg = params["message"]
if(!istext(msg) || !msg)
return "No message set!"
TGS_WORLD_ANNOUNCE(msg)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_PLAYER_COUNT)
return "[TGS_CLIENT_COUNT]"
if(SERVICE_CMD_LIST_CUSTOM)
return json_encode(ListServiceCustomCommands(FALSE))
else
var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM])
if(custom_command_result)
return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS
return "Unknown command: [command]"
/datum/tgs_api/v3210/OnReboot()
switch(reboot_mode)
if(REBOOT_MODE_HARD)
TGS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...")
EndProcess()
if(REBOOT_MODE_SHUTDOWN)
TGS_WORLD_ANNOUNCE("The server is shutting down...")
EndProcess()
else
ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know
/datum/tgs_api/v3210/TestMerges()
//do the best we can here as the datum can't be completed using the v3 api
. = list()
if(!fexists(SERVICE_PR_TEST_JSON))
return
var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON))
if(!json)
return
for(var/I in json)
var/datum/tgs_revision_information/test_merge/tm = new
tm.number = text2num(I)
var/list/entry = json[I]
tm.pull_request_commit = entry["commit"]
tm.author = entry["author"]
tm.title = entry["title"]
. += tm
/datum/tgs_api/v3210/Revision()
if(!warned_revison)
TGS_ERROR_LOG("Use of TgsRevision on [ApiVersion()] origin_commit only points to master!")
warned_revison = TRUE
var/datum/tgs_revision_information/ri = new
ri.commit = commit
ri.origin_commit = originmastercommit
/datum/tgs_api/v3210/EndProcess()
sleep(world.tick_lag) //flush the buffers
ExportService(SERVICE_REQUEST_KILL_PROCESS)
/datum/tgs_api/v3210/ChatChannelInfo()
return list()
/datum/tgs_api/v3210/ChatBroadcast(message, list/channels)
if(channels)
return TGS_UNIMPLEMENTED
ChatTargetedBroadcast(message, TRUE)
ChatTargetedBroadcast(message, FALSE)
/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only)
ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]")
/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only)
return TGS_UNIMPLEMENTED
#undef REBOOT_MODE_NORMAL
#undef REBOOT_MODE_HARD
#undef REBOOT_MODE_SHUTDOWN
#undef SERVICE_WORLD_PARAM
#undef SERVICE_INSTANCE_PARAM
#undef SERVICE_PR_TEST_JSON
#undef SERVICE_INTERFACE_DLL
#undef SERVICE_INTERFACE_FUNCTION
#undef SERVICE_CMD_HARD_REBOOT
#undef SERVICE_CMD_GRACEFUL_SHUTDOWN
#undef SERVICE_CMD_WORLD_ANNOUNCE
#undef SERVICE_CMD_LIST_CUSTOM
#undef SERVICE_CMD_API_COMPATIBLE
#undef SERVICE_CMD_PLAYER_COUNT
#undef SERVICE_CMD_PARAM_KEY
#undef SERVICE_CMD_PARAM_COMMAND
#undef SERVICE_CMD_PARAM_SENDER
#undef SERVICE_CMD_PARAM_CUSTOM
#undef SERVICE_REQUEST_KILL_PROCESS
#undef SERVICE_REQUEST_IRC_BROADCAST
#undef SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE
#undef SERVICE_REQUEST_WORLD_REBOOT
#undef SERVICE_REQUEST_API_VERSION
#undef SERVICE_RETURN_SUCCESS
/*
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.
*/
#define REBOOT_MODE_NORMAL 0
#define REBOOT_MODE_HARD 1
#define REBOOT_MODE_SHUTDOWN 2
#define SERVICE_WORLD_PARAM "server_service"
#define SERVICE_INSTANCE_PARAM "server_instance"
#define SERVICE_PR_TEST_JSON "prtestjob.json"
#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll"
#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint"
#define SERVICE_CMD_HARD_REBOOT "hard_reboot"
#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown"
#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce"
#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands"
#define SERVICE_CMD_API_COMPATIBLE "api_compat"
#define SERVICE_CMD_PLAYER_COUNT "client_count"
#define SERVICE_CMD_PARAM_KEY "serviceCommsKey"
#define SERVICE_CMD_PARAM_COMMAND "command"
#define SERVICE_CMD_PARAM_SENDER "sender"
#define SERVICE_CMD_PARAM_CUSTOM "custom"
#define SERVICE_REQUEST_KILL_PROCESS "killme"
#define SERVICE_REQUEST_IRC_BROADCAST "irc"
#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc"
#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot"
#define SERVICE_REQUEST_API_VERSION "api_ver"
#define SERVICE_RETURN_SUCCESS "SUCCESS"
#define TGS_FILE2LIST(filename) (splittext(trim_left(trim_right(file2text(filename))), "\n"))
/datum/tgs_api/v3210
var/reboot_mode = REBOOT_MODE_NORMAL
var/comms_key
var/instance_name
var/originmastercommit
var/commit
var/list/cached_custom_tgs_chat_commands
var/warned_revison = FALSE
var/warned_custom_commands = FALSE
/datum/tgs_api/v3210/ApiVersion()
return new /datum/tgs_version("3.2.1.3")
/datum/tgs_api/v3210/proc/trim_left(text)
for (var/i = 1 to length(text))
if (text2ascii(text, i) > 32)
return copytext(text, i)
return ""
/datum/tgs_api/v3210/proc/trim_right(text)
for (var/i = length(text), i > 0, i--)
if (text2ascii(text, i) > 32)
return copytext(text, 1, i + 1)
return ""
/datum/tgs_api/v3210/OnWorldNew(minimum_required_security_level)
. = FALSE
comms_key = world.params[SERVICE_WORLD_PARAM]
instance_name = world.params[SERVICE_INSTANCE_PARAM]
if(!instance_name)
instance_name = "TG Station Server" //maybe just upgraded
var/list/logs = TGS_FILE2LIST(".git/logs/HEAD")
if(logs.len)
logs = splittext(logs[logs.len], " ")
if (logs.len >= 2)
commit = logs[2]
else
TGS_ERROR_LOG("Error parsing commit logs")
logs = TGS_FILE2LIST(".git/logs/refs/remotes/origin/master")
if(logs.len)
logs = splittext(logs[logs.len], " ")
if (logs.len >= 2)
originmastercommit = logs[2]
else
TGS_ERROR_LOG("Error parsing origin commmit logs")
if(world.system_type != MS_WINDOWS)
TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!")
return
ListServiceCustomCommands(TRUE)
var/datum/tgs_version/api_version = ApiVersion()
ExportService("[SERVICE_REQUEST_API_VERSION] [api_version.deprefixed_parameter]", TRUE)
return TRUE
//nothing to do for v3
/datum/tgs_api/v3210/OnInitializationComplete()
return
/datum/tgs_api/v3210/InstanceName()
return world.params[SERVICE_INSTANCE_PARAM]
/datum/tgs_api/v3210/proc/ExportService(command, skip_compat_check = FALSE)
. = FALSE
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
return
#if DM_VERSION >= 515
call_ext(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
#else
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
#endif
return TRUE
/datum/tgs_api/v3210/OnTopic(T)
var/list/params = params2list(T)
var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
if(!their_sCK)
return FALSE //continue world/Topic
if(their_sCK != comms_key)
return "Invalid comms key!";
var/command = params[SERVICE_CMD_PARAM_COMMAND]
if(!command)
return "No command!"
switch(command)
if(SERVICE_CMD_API_COMPATIBLE)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_HARD_REBOOT)
if(reboot_mode != REBOOT_MODE_HARD)
reboot_mode = REBOOT_MODE_HARD
TGS_INFO_LOG("Hard reboot requested by service")
TGS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by TGS.")
if(SERVICE_CMD_GRACEFUL_SHUTDOWN)
if(reboot_mode != REBOOT_MODE_SHUTDOWN)
reboot_mode = REBOOT_MODE_SHUTDOWN
TGS_INFO_LOG("Shutdown requested by service")
TGS_NOTIFY_ADMINS("The world will shutdown at the end of the game. Requested by TGS.")
if(SERVICE_CMD_WORLD_ANNOUNCE)
var/msg = params["message"]
if(!istext(msg) || !msg)
return "No message set!"
TGS_WORLD_ANNOUNCE(msg)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_PLAYER_COUNT)
return "[TGS_CLIENT_COUNT]"
if(SERVICE_CMD_LIST_CUSTOM)
return json_encode(ListServiceCustomCommands(FALSE))
else
var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM])
if(custom_command_result)
return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS
return "Unknown command: [command]"
/datum/tgs_api/v3210/OnReboot()
switch(reboot_mode)
if(REBOOT_MODE_HARD)
TGS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...")
EndProcess()
if(REBOOT_MODE_SHUTDOWN)
TGS_WORLD_ANNOUNCE("The server is shutting down...")
EndProcess()
else
ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know
/datum/tgs_api/v3210/TestMerges()
//do the best we can here as the datum can't be completed using the v3 api
. = list()
if(!fexists(SERVICE_PR_TEST_JSON))
return
var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON))
if(!json)
return
for(var/I in json)
var/datum/tgs_revision_information/test_merge/tm = new
tm.number = text2num(I)
var/list/entry = json[I]
tm.head_commit = entry["commit"]
tm.author = entry["author"]
tm.title = entry["title"]
. += tm
/datum/tgs_api/v3210/Revision()
if(!warned_revison)
var/datum/tgs_version/api_version = ApiVersion()
TGS_WARNING_LOG("Use of TgsRevision on [api_version.deprefixed_parameter] origin_commit only points to master!")
warned_revison = TRUE
var/datum/tgs_revision_information/ri = new
ri.commit = commit
ri.origin_commit = originmastercommit
return ri
/datum/tgs_api/v3210/EndProcess()
sleep(world.tick_lag) //flush the buffers
ExportService(SERVICE_REQUEST_KILL_PROCESS)
/datum/tgs_api/v3210/ChatChannelInfo()
return list() // :omegalul:
/datum/tgs_api/v3210/ChatBroadcast(datum/tgs_message_content/message, list/channels)
if(channels)
return TGS_UNIMPLEMENTED
message = UpgradeDeprecatedChatMessage(message)
ChatTargetedBroadcast(message, TRUE)
ChatTargetedBroadcast(message, FALSE)
/datum/tgs_api/v3210/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only)
message = UpgradeDeprecatedChatMessage(message)
ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message.text]")
/datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user)
UpgradeDeprecatedChatMessage(message)
return TGS_UNIMPLEMENTED
/datum/tgs_api/v3210/SecurityLevel()
return TGS_SECURITY_TRUSTED
#undef REBOOT_MODE_NORMAL
#undef REBOOT_MODE_HARD
#undef REBOOT_MODE_SHUTDOWN
#undef SERVICE_WORLD_PARAM
#undef SERVICE_INSTANCE_PARAM
#undef SERVICE_PR_TEST_JSON
#undef SERVICE_INTERFACE_DLL
#undef SERVICE_INTERFACE_FUNCTION
#undef SERVICE_CMD_HARD_REBOOT
#undef SERVICE_CMD_GRACEFUL_SHUTDOWN
#undef SERVICE_CMD_WORLD_ANNOUNCE
#undef SERVICE_CMD_LIST_CUSTOM
#undef SERVICE_CMD_API_COMPATIBLE
#undef SERVICE_CMD_PLAYER_COUNT
#undef SERVICE_CMD_PARAM_KEY
#undef SERVICE_CMD_PARAM_COMMAND
#undef SERVICE_CMD_PARAM_SENDER
#undef SERVICE_CMD_PARAM_CUSTOM
#undef SERVICE_REQUEST_KILL_PROCESS
#undef SERVICE_REQUEST_IRC_BROADCAST
#undef SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE
#undef SERVICE_REQUEST_WORLD_REBOOT
#undef SERVICE_REQUEST_API_VERSION
#undef SERVICE_RETURN_SUCCESS
#undef TGS_FILE2LIST
+58 -78
View File
@@ -1,78 +1,58 @@
#define SERVICE_JSON_PARAM_HELPTEXT "help_text"
#define SERVICE_JSON_PARAM_ADMINONLY "admin_only"
#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters"
/datum/tgs_api/v3210/proc/ListServiceCustomCommands(warnings_only)
if(!warnings_only)
. = list()
var/list/command_name_types = list()
var/list/warned_command_names = warnings_only ? list() : null
var/warned_about_the_dangers_of_robutussin = !warnings_only
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
if(!warned_about_the_dangers_of_robutussin)
TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
warned_about_the_dangers_of_robutussin = TRUE
var/datum/tgs_chat_command/stc = I
var/command_name = initial(stc.name)
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
if(warnings_only && !warned_command_names[command_name])
TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!")
warned_command_names[command_name] = TRUE
continue
if(command_name_types[command_name])
if(warnings_only)
TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!")
continue
command_name_types[stc] = command_name
if(!warnings_only)
.[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = 0)
/datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params)
if(!cached_custom_tgs_chat_commands)
cached_custom_tgs_chat_commands = list()
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
var/datum/tgs_chat_command/stc = I
cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc
var/command_type = cached_custom_tgs_chat_commands[command]
if(!command_type)
return FALSE
var/datum/tgs_chat_command/stc = new command_type
var/datum/tgs_chat_user/user = new
user.friendly_name = sender
user.mention = sender
return stc.Run(user, params) || TRUE
/*
#undef SERVICE_JSON_PARAM_HELPTEXT
#undef SERVICE_JSON_PARAM_ADMINONLY
#undef SERVICE_JSON_PARAM_REQUIREDPARAMETERS
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.
*/
#define SERVICE_JSON_PARAM_HELPTEXT "help_text"
#define SERVICE_JSON_PARAM_ADMINONLY "admin_only"
#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters"
/datum/tgs_api/v3210/proc/ListServiceCustomCommands(warnings_only)
if(!warnings_only)
. = list()
var/list/command_name_types = list()
var/list/warned_command_names = warnings_only ? list() : null
var/warned_about_the_dangers_of_robutussin = !warnings_only
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
if(!warned_about_the_dangers_of_robutussin)
TGS_WARNING_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
warned_about_the_dangers_of_robutussin = TRUE
var/datum/tgs_chat_command/stc = I
if(stc.ignore_type == I)
continue
var/command_name = initial(stc.name)
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
if(warnings_only && !warned_command_names[command_name])
TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!")
warned_command_names[command_name] = TRUE
continue
if(command_name_types[command_name])
if(warnings_only)
TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!")
continue
command_name_types[stc] = command_name
if(!warnings_only)
.[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = 0)
/datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params)
if(!cached_custom_tgs_chat_commands)
cached_custom_tgs_chat_commands = list()
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
var/datum/tgs_chat_command/stc = I
cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc
var/command_type = cached_custom_tgs_chat_commands[command]
if(!command_type)
return FALSE
var/datum/tgs_chat_command/stc = new command_type
var/datum/tgs_chat_user/user = new
user.friendly_name = sender
// Discord hack, fix the mention if it's only numbers (fuck you IRC trolls)
var/regex/discord_id_regex = regex("^\[0-9\]+$")
if(findtext(sender, discord_id_regex))
sender = "<@[sender]>"
user.mention = sender
var/datum/tgs_message_content/result = stc.Run(user, params)
result = UpgradeDeprecatedCommandResponse(result, command)
return result ? result.text : TRUE
+322
View File
@@ -0,0 +1,322 @@
#define TGS4_PARAM_INFO_JSON "tgs_json"
#define TGS4_INTEROP_ACCESS_IDENTIFIER "tgs_tok"
#define TGS4_RESPONSE_SUCCESS "tgs_succ"
#define TGS4_TOPIC_CHANGE_PORT "tgs_port"
#define TGS4_TOPIC_CHANGE_REBOOT_MODE "tgs_rmode"
#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm"
#define TGS4_TOPIC_EVENT "tgs_event"
#define TGS4_TOPIC_INTEROP_RESPONSE "tgs_interop"
#define TGS4_COMM_NEW_PORT "tgs_new_port"
#define TGS4_COMM_VALIDATE "tgs_validate"
#define TGS4_COMM_SERVER_PRIMED "tgs_prime"
#define TGS4_COMM_WORLD_REBOOT "tgs_reboot"
#define TGS4_COMM_END_PROCESS "tgs_kill"
#define TGS4_COMM_CHAT "tgs_chat_send"
#define TGS4_PARAMETER_COMMAND "tgs_com"
#define TGS4_PARAMETER_DATA "tgs_data"
#define TGS4_PORT_CRITFAIL_MESSAGE " Must exit to let watchdog reboot..."
#define EXPORT_TIMEOUT_DS 200
/datum/tgs_api/v4
var/access_identifier
var/instance_name
var/json_path
var/chat_channels_json_path
var/chat_commands_json_path
var/server_commands_json_path
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
var/security_level
var/requesting_new_port = FALSE
var/list/intercepted_message_queue
var/list/custom_commands
var/list/cached_test_merges
var/datum/tgs_revision_information/cached_revision
var/export_lock = FALSE
var/list/last_interop_response
/datum/tgs_api/v4/ApiVersion()
return new /datum/tgs_version("4.0.0.0")
/datum/tgs_api/v4/OnWorldNew(minimum_required_security_level)
if(minimum_required_security_level == TGS_SECURITY_ULTRASAFE)
TGS_WARNING_LOG("V4 DMAPI requires safe security!")
minimum_required_security_level = TGS_SECURITY_SAFE
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
var/cached_json = json_decode(json_file)
if(!cached_json)
TGS_ERROR_LOG("Failed to decode info json: [json_file]")
return
access_identifier = cached_json["accessIdentifier"]
server_commands_json_path = cached_json["serverCommandsJson"]
if(cached_json["apiValidateOnly"])
TGS_INFO_LOG("Validating API and exiting...")
Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]"))
TerminateWorld()
security_level = cached_json["securityLevel"]
chat_channels_json_path = cached_json["chatChannelsJson"]
chat_commands_json_path = cached_json["chatCommandsJson"]
instance_name = cached_json["instanceName"]
ListCustomCommands()
var/list/revisionData = cached_json["revision"]
if(revisionData)
cached_revision = new
cached_revision.commit = revisionData["commitSha"]
cached_revision.origin_commit = revisionData["originCommitSha"]
cached_test_merges = list()
var/list/json = cached_json["testMerges"]
for(var/entry in json)
var/datum/tgs_revision_information/test_merge/tm = new
tm.timestamp = text2num(entry["timeMerged"])
var/list/revInfo = entry["revision"]
if(revInfo)
tm.commit = revInfo["commitSha"]
tm.origin_commit = revInfo["originCommitSha"]
tm.title = entry["titleAtMerge"]
tm.body = entry["bodyAtMerge"]
tm.url = entry["url"]
tm.author = entry["author"]
tm.number = entry["number"]
tm.head_commit = entry["pullRequestRevision"]
tm.comment = entry["comment"]
cached_test_merges += tm
return TRUE
/datum/tgs_api/v4/OnInitializationComplete()
Export(TGS4_COMM_SERVER_PRIMED)
/datum/tgs_api/v4/OnTopic(T)
var/list/params = params2list(T)
var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER]
if(!their_sCK)
return FALSE //continue world/Topic
if(their_sCK != access_identifier)
return "Invalid comms key!";
var/command = params[TGS4_PARAMETER_COMMAND]
if(!command)
return "No command!"
. = TGS4_RESPONSE_SUCCESS
switch(command)
if(TGS4_TOPIC_CHAT_COMMAND)
var/result = HandleCustomCommand(params[TGS4_PARAMETER_DATA])
if(result == null)
result = "Error running chat command!"
return result
if(TGS4_TOPIC_EVENT)
intercepted_message_queue = list()
var/list/event_notification = json_decode(params[TGS4_PARAMETER_DATA])
var/list/event_parameters = event_notification["Parameters"]
var/list/event_call = list(event_notification["Type"])
if(event_parameters)
event_call += event_parameters
if(event_handler != null)
event_handler.HandleEvent(arglist(event_call))
. = json_encode(intercepted_message_queue)
intercepted_message_queue = null
return
if(TGS4_TOPIC_INTEROP_RESPONSE)
last_interop_response = json_decode(params[TGS4_PARAMETER_DATA])
return
if(TGS4_TOPIC_CHANGE_PORT)
var/new_port = text2num(params[TGS4_PARAMETER_DATA])
if (!(new_port > 0))
return "Invalid port: [new_port]"
//the topic still completes, miraculously
//I honestly didn't believe byond could do it
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
if(!world.OpenPort(new_port))
return "Port change failed!"
return
if(TGS4_TOPIC_CHANGE_REBOOT_MODE)
var/new_reboot_mode = text2num(params[TGS4_PARAMETER_DATA])
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode)
reboot_mode = new_reboot_mode
return
return "Unknown command: [command]"
/datum/tgs_api/v4/proc/Export(command, list/data, override_requesting_new_port = FALSE)
if(!data)
data = list()
data[TGS4_PARAMETER_COMMAND] = command
var/json = json_encode(data)
while(requesting_new_port && !override_requesting_new_port)
sleep(1)
//we need some port open at this point to facilitate return communication
if(!world.port)
requesting_new_port = TRUE
if(!world.OpenPort(0)) //open any port
TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]")
TerminateWorld()
//request a new port
export_lock = FALSE
var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
if(!new_port_json)
TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]")
TerminateWorld()
var/new_port = new_port_json[TGS4_PARAMETER_DATA]
if(!isnum(new_port) || new_port <= 0)
TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]")
TerminateWorld()
if(new_port != world.port && !world.OpenPort(new_port))
TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]")
TerminateWorld()
requesting_new_port = FALSE
while(export_lock)
sleep(1)
export_lock = TRUE
last_interop_response = null
fdel(server_commands_json_path)
text2file(json, server_commands_json_path)
for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I)
sleep(1)
if(!last_interop_response)
TGS_ERROR_LOG("Failed to get export result for: [json]")
else
. = last_interop_response
export_lock = FALSE
/datum/tgs_api/v4/OnReboot()
var/list/result = Export(TGS4_COMM_WORLD_REBOOT)
if(!result)
return
//okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter
var/port = result[TGS4_PARAMETER_DATA]
if(!isnum(port))
return //this is valid, server may just want use to reboot
if(port == 0)
//to byond 0 means any port and "none" means close vOv
port = "none"
if(!world.OpenPort(port))
TGS_ERROR_LOG("Unable to set port to [port]!")
/datum/tgs_api/v4/InstanceName()
return instance_name
/datum/tgs_api/v4/TestMerges()
return cached_test_merges.Copy()
/datum/tgs_api/v4/EndProcess()
Export(TGS4_COMM_END_PROCESS)
/datum/tgs_api/v4/Revision()
return cached_revision
/datum/tgs_api/v4/ChatBroadcast(datum/tgs_message_content/message, list/channels)
var/list/ids
if(length(channels))
ids = list()
for(var/I in channels)
var/datum/tgs_chat_channel/channel = I
ids += channel.id
message = UpgradeDeprecatedChatMessage(message)
if (!length(channels))
return
message = list("message" = message.text, "channelIds" = ids)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Export(TGS4_COMM_CHAT, message)
/datum/tgs_api/v4/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only)
var/list/channels = list()
for(var/I in ChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only)))
channels += channel.id
message = UpgradeDeprecatedChatMessage(message)
if (!length(channels))
return
message = list("message" = message.text, "channelIds" = channels)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Export(TGS4_COMM_CHAT, message)
/datum/tgs_api/v4/ChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user)
message = UpgradeDeprecatedChatMessage(message)
message = list("message" = message.text, "channelIds" = list(user.channel.id))
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Export(TGS4_COMM_CHAT, message)
/datum/tgs_api/v4/ChatChannelInfo()
. = list()
//no caching cause tgs may change this
var/list/json = json_decode(file2text(chat_channels_json_path))
for(var/I in json)
. += DecodeChannel(I)
/datum/tgs_api/v4/proc/DecodeChannel(channel_json)
var/datum/tgs_chat_channel/channel = new
channel.id = channel_json["id"]
channel.friendly_name = channel_json["friendlyName"]
channel.connection_name = channel_json["connectionName"]
channel.is_admin_channel = channel_json["isAdminChannel"]
channel.is_private_channel = channel_json["isPrivateChannel"]
channel.custom_tag = channel_json["tag"]
return channel
/datum/tgs_api/v4/SecurityLevel()
return security_level
+44
View File
@@ -0,0 +1,44 @@
/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
if(stc.ignore_type == I)
continue
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 += list(list("name" = command_name, "help_text" = stc.help_text, "admin_only" = stc.admin_only))
custom_commands[command_name] = stc
var/commands_file = chat_commands_json_path
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["friendlyName"]
u.mention = user["mention"]
u.channel = DecodeChannel(user["channel"])
var/datum/tgs_chat_command/sc = custom_commands[command]
if(sc)
var/datum/tgs_message_content/result = sc.Run(u, params)
result = UpgradeDeprecatedCommandResponse(result, command)
return result ? result.text : TRUE
return "Unknown command: [command]!"
+1
View File
@@ -0,0 +1 @@
"5.8.0"
+118
View File
@@ -0,0 +1,118 @@
#define DMAPI5_PARAM_SERVER_PORT "tgs_port"
#define DMAPI5_PARAM_ACCESS_IDENTIFIER "tgs_key"
#define DMAPI5_BRIDGE_DATA "data"
#define DMAPI5_TOPIC_DATA "tgs_data"
#define DMAPI5_BRIDGE_REQUEST_LIMIT 8198
#define DMAPI5_TOPIC_REQUEST_LIMIT 65528
#define DMAPI5_TOPIC_RESPONSE_LIMIT 65529
#define DMAPI5_BRIDGE_COMMAND_STARTUP 1
#define DMAPI5_BRIDGE_COMMAND_PRIME 2
#define DMAPI5_BRIDGE_COMMAND_REBOOT 3
#define DMAPI5_BRIDGE_COMMAND_KILL 4
#define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5
#define DMAPI5_BRIDGE_COMMAND_CHUNK 6
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
#define DMAPI5_PARAMETER_TOPIC_PORT "topicPort"
#define DMAPI5_CHUNK "chunk"
#define DMAPI5_CHUNK_PAYLOAD "payload"
#define DMAPI5_CHUNK_TOTAL "totalChunks"
#define DMAPI5_CHUNK_SEQUENCE_ID "sequenceId"
#define DMAPI5_CHUNK_PAYLOAD_ID "payloadId"
#define DMAPI5_MISSING_CHUNKS "missingChunks"
#define DMAPI5_RESPONSE_ERROR_MESSAGE "errorMessage"
#define DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT "currentPort"
#define DMAPI5_BRIDGE_PARAMETER_VERSION "version"
#define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage"
#define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel"
#define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort"
#define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation"
#define DMAPI5_CHAT_MESSAGE_CHANNEL_IDS "channelIds"
#define DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION "serverVersion"
#define DMAPI5_RUNTIME_INFORMATION_SERVER_PORT "serverPort"
#define DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY "apiValidateOnly"
#define DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME "instanceName"
#define DMAPI5_RUNTIME_INFORMATION_REVISION "revision"
#define DMAPI5_RUNTIME_INFORMATION_TEST_MERGES "testMerges"
#define DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL "securityLevel"
#define DMAPI5_RUNTIME_INFORMATION_VISIBILITY "visibility"
#define DMAPI5_CHAT_UPDATE_CHANNELS "channels"
#define DMAPI5_TEST_MERGE_TIME_MERGED "timeMerged"
#define DMAPI5_TEST_MERGE_REVISION "revision"
#define DMAPI5_TEST_MERGE_TITLE_AT_MERGE "titleAtMerge"
#define DMAPI5_TEST_MERGE_BODY_AT_MERGE "bodyAtMerge"
#define DMAPI5_TEST_MERGE_URL "url"
#define DMAPI5_TEST_MERGE_AUTHOR "author"
#define DMAPI5_TEST_MERGE_NUMBER "number"
#define DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION "pullRequestRevision"
#define DMAPI5_TEST_MERGE_COMMENT "comment"
#define DMAPI5_CHAT_COMMAND_NAME "name"
#define DMAPI5_CHAT_COMMAND_PARAMS "params"
#define DMAPI5_CHAT_COMMAND_USER "user"
#define DMAPI5_EVENT_NOTIFICATION_TYPE "type"
#define DMAPI5_EVENT_NOTIFICATION_PARAMETERS "parameters"
#define DMAPI5_TOPIC_COMMAND_CHAT_COMMAND 0
#define DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION 1
#define DMAPI5_TOPIC_COMMAND_CHANGE_PORT 2
#define DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE 3
#define DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED 4
#define DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE 5
#define DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE 6
#define DMAPI5_TOPIC_COMMAND_HEALTHCHECK 7
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11
#define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
#define DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION "eventNotification"
#define DMAPI5_TOPIC_PARAMETER_NEW_PORT "newPort"
#define DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE "newRebootState"
#define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName"
#define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate"
#define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion"
#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage"
#define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses"
#define DMAPI5_REVISION_INFORMATION_COMMIT_SHA "commitSha"
#define DMAPI5_REVISION_INFORMATION_TIMESTAMP "timestamp"
#define DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA "originCommitSha"
#define DMAPI5_CHAT_USER_ID "id"
#define DMAPI5_CHAT_USER_FRIENDLY_NAME "friendlyName"
#define DMAPI5_CHAT_USER_MENTION "mention"
#define DMAPI5_CHAT_USER_CHANNEL "channel"
#define DMAPI5_CHAT_CHANNEL_ID "id"
#define DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME "friendlyName"
#define DMAPI5_CHAT_CHANNEL_CONNECTION_NAME "connectionName"
#define DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL "isAdminChannel"
#define DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL "isPrivateChannel"
#define DMAPI5_CHAT_CHANNEL_TAG "tag"
#define DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED "embedsSupported"
#define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name"
#define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText"
#define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly"
+281
View File
@@ -0,0 +1,281 @@
/datum/tgs_api/v5
var/server_port
var/access_identifier
var/instance_name
var/security_level
var/visibility
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/intercepted_message_queue
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/offline_message_queue
var/list/custom_commands
var/list/test_merges
var/datum/tgs_revision_information/revision
var/list/chat_channels
var/initialized = FALSE
var/initial_bridge_request_received = FALSE
var/datum/tgs_version/interop_version
var/chunked_requests = 0
var/list/chunked_topics = list()
var/detached = FALSE
/datum/tgs_api/v5/New()
. = ..()
interop_version = version
TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
#include "__interop_version.dm"
)
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
TGS_DEBUG_LOG("OnWorldNew()")
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER]
var/datum/tgs_version/api_version = ApiVersion()
version = null // we want this to be the TGS version, not the interop version
var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
if(!istype(bridge_response))
TGS_ERROR_LOG("Failed initial bridge request!")
return FALSE
var/list/runtime_information = bridge_response[DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION]
if(!istype(runtime_information))
TGS_ERROR_LOG("Failed to decode runtime information from bridge response: [json_encode(bridge_response)]!")
return FALSE
if(runtime_information[DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY])
TGS_INFO_LOG("DMAPI validation, exiting...")
TerminateWorld()
initial_bridge_request_received = TRUE
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY]
instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME]
var/list/revisionData = runtime_information[DMAPI5_RUNTIME_INFORMATION_REVISION]
if(istype(revisionData))
revision = new
revision.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
revision.timestamp = revisionData[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
revision.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
else
TGS_ERROR_LOG("Failed to decode [DMAPI5_RUNTIME_INFORMATION_REVISION] from runtime information!")
test_merges = list()
var/list/test_merge_json = runtime_information[DMAPI5_RUNTIME_INFORMATION_TEST_MERGES]
if(istype(test_merge_json))
for(var/entry in test_merge_json)
var/datum/tgs_revision_information/test_merge/tm = new
tm.number = entry[DMAPI5_TEST_MERGE_NUMBER]
var/list/revInfo = entry[DMAPI5_TEST_MERGE_REVISION]
if(revInfo)
tm.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
tm.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
tm.timestamp = entry[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!")
if(!tm.timestamp)
tm.timestamp = entry[DMAPI5_TEST_MERGE_TIME_MERGED]
tm.title = entry[DMAPI5_TEST_MERGE_TITLE_AT_MERGE]
tm.body = entry[DMAPI5_TEST_MERGE_BODY_AT_MERGE]
tm.url = entry[DMAPI5_TEST_MERGE_URL]
tm.author = entry[DMAPI5_TEST_MERGE_AUTHOR]
tm.head_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION]
tm.comment = entry[DMAPI5_TEST_MERGE_COMMENT]
test_merges += tm
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_RUNTIME_INFORMATION_TEST_MERGES] from runtime information!")
chat_channels = list()
DecodeChannels(runtime_information)
initialized = TRUE
return TRUE
/datum/tgs_api/v5/proc/GetTopicPort()
#if defined(OPENDREAM) && defined(OPENDREAM_TOPIC_PORT_EXISTS)
return "[world.opendream_topic_port]"
#else
return null
#endif
/datum/tgs_api/v5/proc/RequireInitialBridgeResponse()
TGS_DEBUG_LOG("RequireInitialBridgeResponse()")
var/logged = FALSE
while(!initial_bridge_request_received)
if(!logged)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
logged = TRUE
sleep(1)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed")
/datum/tgs_api/v5/OnInitializationComplete()
Bridge(DMAPI5_BRIDGE_COMMAND_PRIME)
/datum/tgs_api/v5/OnTopic(T)
TGS_DEBUG_LOG("OnTopic()")
RequireInitialBridgeResponse()
TGS_DEBUG_LOG("OnTopic passed bridge request gate")
var/list/params = params2list(T)
var/json = params[DMAPI5_TOPIC_DATA]
if(!json)
TGS_DEBUG_LOG("No \"[DMAPI5_TOPIC_DATA]\" entry found, ignoring...")
return FALSE // continue to /world/Topic
if(!initialized)
TGS_WARNING_LOG("Missed topic due to not being initialized: [json]")
return TRUE // too early to handle, but it's still our responsibility
return ProcessTopicJson(json, TRUE)
/datum/tgs_api/v5/OnReboot()
var/list/result = Bridge(DMAPI5_BRIDGE_COMMAND_REBOOT)
if(!result)
return
//okay so the standard TGS proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter
var/port = result[DMAPI5_BRIDGE_RESPONSE_NEW_PORT]
if(!isnum(port))
return //this is valid, server may just want use to reboot
if(port == 0)
//to byond 0 means any port and "none" means close vOv
port = "none"
if(!world.OpenPort(port))
TGS_ERROR_LOG("Unable to set port to [port]!")
/datum/tgs_api/v5/InstanceName()
RequireInitialBridgeResponse()
return instance_name
/datum/tgs_api/v5/TestMerges()
RequireInitialBridgeResponse()
return test_merges.Copy()
/datum/tgs_api/v5/EndProcess()
Bridge(DMAPI5_BRIDGE_COMMAND_KILL)
/datum/tgs_api/v5/Revision()
RequireInitialBridgeResponse()
return revision
// Common proc b/c it's used by the V3/V4 APIs
/datum/tgs_api/proc/UpgradeDeprecatedChatMessage(datum/tgs_message_content/message)
if(!istext(message))
return message
TGS_WARNING_LOG("Received legacy string when a [/datum/tgs_message_content] was expected. Please audit all calls to TgsChatBroadcast, TgsChatTargetedBroadcast, and TgsChatPrivateMessage to ensure they use the new /datum.")
return new /datum/tgs_message_content(message)
/datum/tgs_api/v5/ChatBroadcast(datum/tgs_message_content/message2, list/channels)
if(!length(channels))
channels = ChatChannelInfo()
var/list/ids = list()
for(var/I in channels)
var/datum/tgs_chat_channel/channel = I
ids += channel.id
SendChatMessageRaw(message2, ids)
/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only)
var/list/channels = list()
for(var/I in ChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only)))
channels += channel.id
SendChatMessageRaw(message2, channels)
/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user)
SendChatMessageRaw(message2, list(user.channel.id))
/datum/tgs_api/v5/proc/SendChatMessageRaw(datum/tgs_message_content/message2, list/channel_ids)
message2 = UpgradeDeprecatedChatMessage(message2)
if (!length(channel_ids))
return
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids
if(intercepted_message_queue)
intercepted_message_queue += list(data)
return
if(offline_message_queue)
offline_message_queue += list(data)
return
if(detached)
offline_message_queue = list(data)
WaitForReattach(FALSE)
data = offline_message_queue
offline_message_queue = null
for(var/queued_message in data)
SendChatDataRaw(queued_message)
else
SendChatDataRaw(data)
/datum/tgs_api/v5/proc/SendChatDataRaw(list/data)
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
/datum/tgs_api/v5/ChatChannelInfo()
RequireInitialBridgeResponse()
WaitForReattach(TRUE)
return chat_channels.Copy()
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
TGS_DEBUG_LOG("DecodeChannels()")
var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS]
if(istype(chat_channels_json))
chat_channels.Cut()
for(var/channel_json in chat_channels_json)
var/datum/tgs_chat_channel/channel = DecodeChannel(channel_json)
if(channel)
chat_channels += channel
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_CHAT_UPDATE_CHANNELS] from channel update!")
/datum/tgs_api/v5/proc/DecodeChannel(channel_json)
var/datum/tgs_chat_channel/channel = new
channel.id = channel_json[DMAPI5_CHAT_CHANNEL_ID]
channel.friendly_name = channel_json[DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME]
channel.connection_name = channel_json[DMAPI5_CHAT_CHANNEL_CONNECTION_NAME]
channel.is_admin_channel = channel_json[DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL]
channel.is_private_channel = channel_json[DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL]
channel.custom_tag = channel_json[DMAPI5_CHAT_CHANNEL_TAG]
channel.embeds_supported = channel_json[DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED]
return channel
/datum/tgs_api/v5/SecurityLevel()
RequireInitialBridgeResponse()
return security_level
/datum/tgs_api/v5/Visibility()
RequireInitialBridgeResponse()
return visibility
+106
View File
@@ -0,0 +1,106 @@
/datum/tgs_api/v5/proc/Bridge(command, list/data)
if(!data)
data = list()
var/single_bridge_request = CreateBridgeRequest(command, data)
if(length(single_bridge_request) <= DMAPI5_BRIDGE_REQUEST_LIMIT)
return PerformBridgeRequest(single_bridge_request)
// chunking required
var/payload_id = ++chunked_requests
var/raw_data = CreateBridgeData(command, data, FALSE)
var/list/chunk_requests = GenerateChunks(raw_data, TRUE)
var/list/response
for(var/bridge_request in chunk_requests)
response = PerformBridgeRequest(bridge_request)
if(!response)
// Abort
return
var/list/missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS]
if(length(missing_sequence_ids))
do
TGS_WARNING_LOG("Server is still missing some chunks of bridge P[payload_id]! Sending missing chunks...")
if(!istype(missing_sequence_ids))
TGS_ERROR_LOG("Did not receive a list() for [DMAPI5_MISSING_CHUNKS]!")
return
for(var/missing_sequence_id in missing_sequence_ids)
if(!isnum(missing_sequence_id))
TGS_ERROR_LOG("Did not receive a num in [DMAPI5_MISSING_CHUNKS]!")
return
var/missing_chunk_request = chunk_requests[missing_sequence_id + 1]
response = PerformBridgeRequest(missing_chunk_request)
if(!response)
// Abort
return
missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS]
while(length(missing_sequence_ids))
return response
/datum/tgs_api/v5/proc/CreateBridgeRequest(command, list/data)
var/json = CreateBridgeData(command, data, TRUE)
var/encoded_json = url_encode(json)
var/api_prefix = interop_version.minor >= 8 ? "api/" : ""
var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
return url
/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth)
data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command
if(needs_auth)
data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier
var/json = json_encode(data)
return json
/datum/tgs_api/v5/proc/WaitForReattach(require_channels = FALSE)
if(detached)
// Wait up to one minute
for(var/i in 1 to 600)
sleep(1)
if(!detached && (!require_channels || length(chat_channels)))
break
// dad went out for milk and cigarettes 20 years ago...
// yes, this affects all other waiters, intentional
if(i == 600)
detached = FALSE
/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request)
WaitForReattach(FALSE)
// This is an infinite sleep until we get a response
var/export_response = world.Export(bridge_request)
if(!export_response)
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
return
var/content = export_response["CONTENT"]
if(!content)
TGS_ERROR_LOG("Failed bridge request, missing content!")
return
var/response_json = file2text(content)
if(!response_json)
TGS_ERROR_LOG("Failed bridge request, failed to load content!")
return
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
return
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
if(error)
TGS_ERROR_LOG("Failed bridge request, bad request: [error]")
return
return bridge_response
+43
View File
@@ -0,0 +1,43 @@
/datum/tgs_api/v5/proc/GenerateChunks(payload, bridge)
var/limit = bridge ? DMAPI5_BRIDGE_REQUEST_LIMIT : DMAPI5_TOPIC_RESPONSE_LIMIT
var/payload_id = ++chunked_requests
var/data_length = length(payload)
var/chunk_count
var/list/chunk_requests
for(chunk_count = 2; !chunk_requests; ++chunk_count)
var/max_chunk_size = -round(-(data_length / chunk_count))
if(max_chunk_size > limit)
continue
chunk_requests = list()
for(var/i in 1 to chunk_count)
var/start_index = 1 + ((i - 1) * max_chunk_size)
if (start_index > data_length)
break
var/end_index = min(1 + (i * max_chunk_size), data_length + 1)
var/chunk_payload = copytext(payload, start_index, end_index)
// sequence IDs in interop chunking are always zero indexed
var/list/chunk = list(DMAPI5_CHUNK_PAYLOAD_ID = payload_id, DMAPI5_CHUNK_SEQUENCE_ID = (i - 1), DMAPI5_CHUNK_TOTAL = chunk_count, DMAPI5_CHUNK_PAYLOAD = chunk_payload)
var/chunk_request = list(DMAPI5_CHUNK = chunk)
var/chunk_length
if(bridge)
chunk_request = CreateBridgeRequest(DMAPI5_BRIDGE_COMMAND_CHUNK, chunk_request)
chunk_length = length(chunk_request)
else
chunk_request = list(chunk_request) // wrap for adding to list
chunk_length = length(json_encode(chunk_request))
if(chunk_length > limit)
// Screwed by encoding, no way to preempt it though
chunk_requests = null
break
chunk_requests += chunk_request
return chunk_requests
+60
View File
@@ -0,0 +1,60 @@
/datum/tgs_api/v5/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
if(stc.ignore_type == I)
continue
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 += list(list(DMAPI5_CUSTOM_CHAT_COMMAND_NAME = command_name, DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT = stc.help_text, DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY = stc.admin_only))
custom_commands[command_name] = stc
return results
/datum/tgs_api/v5/proc/HandleCustomCommand(list/command_json)
var/command = command_json[DMAPI5_CHAT_COMMAND_NAME]
var/user = command_json[DMAPI5_CHAT_COMMAND_USER]
var/params = command_json[DMAPI5_CHAT_COMMAND_PARAMS]
var/datum/tgs_chat_user/u = new
u.id = user[DMAPI5_CHAT_USER_ID]
u.friendly_name = user[DMAPI5_CHAT_USER_FRIENDLY_NAME]
u.mention = user[DMAPI5_CHAT_USER_MENTION]
u.channel = DecodeChannel(user[DMAPI5_CHAT_USER_CHANNEL])
var/datum/tgs_chat_command/sc = custom_commands[command]
if(sc)
var/datum/tgs_message_content/response = sc.Run(u, params)
response = UpgradeDeprecatedCommandResponse(response, command)
var/list/topic_response = TopicResponse()
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = response ? response.text : null
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE] = response ? response._interop_serialize() : null
return topic_response
return TopicResponse("Unknown custom chat command: [command]!")
// Common proc b/c it's used by the V3/V4 APIs
/datum/tgs_api/proc/UpgradeDeprecatedCommandResponse(datum/tgs_message_content/response, command)
// Backwards compatibility, used to return a string
if(istext(response))
warned_deprecated_command_runs = warned_deprecated_command_runs || list()
if(!warned_deprecated_command_runs[command])
TGS_WARNING_LOG("Custom chat command \"[command]\" is still returning a string. This behaviour is deprecated, please upgrade it to return a [/datum/tgs_message_content].")
warned_deprecated_command_runs[command] = TRUE
return new /datum/tgs_message_content(response)
if(!istype(response))
TGS_ERROR_LOG("Custom chat command \"[command]\" should return a [/datum/tgs_message_content]! Got: \"[response]\"")
return null
return response
+59
View File
@@ -0,0 +1,59 @@
/datum/tgs_message_content/proc/_interop_serialize()
return list("text" = text, "embed" = embed ? embed._interop_serialize() : null)
/datum/tgs_chat_embed/proc/_interop_serialize()
CRASH("Base /proc/interop_serialize called on [type]!")
/datum/tgs_chat_embed/structure/_interop_serialize()
var/list/serialized_fields
if(istype(fields, /list))
serialized_fields = list()
for(var/datum/tgs_chat_embed/field/field as anything in fields)
serialized_fields += list(field._interop_serialize())
return list(
"title" = title,
"description" = description,
"url" = url,
"timestamp" = timestamp,
"colour" = colour,
"image" = src.image ? src.image._interop_serialize() : null,
"thumbnail" = thumbnail ? thumbnail._interop_serialize() : null,
"video" = video ? video._interop_serialize() : null,
"footer" = footer ? footer._interop_serialize() : null,
"provider" = provider ? provider._interop_serialize() : null,
"author" = author ? author._interop_serialize() : null,
"fields" = serialized_fields
)
/datum/tgs_chat_embed/media/_interop_serialize()
return list(
"url" = url,
"width" = width,
"height" = height,
"proxyUrl" = proxy_url
)
/datum/tgs_chat_embed/provider/_interop_serialize()
return list(
"url" = url,
"name" = name
)
/datum/tgs_chat_embed/provider/author/_interop_serialize()
. = ..()
.["iconUrl"] = icon_url
.["proxyIconUrl"] = proxy_icon_url
/datum/tgs_chat_embed/footer/_interop_serialize()
return list(
"text" = text,
"iconUrl" = icon_url,
"proxyIconUrl" = proxy_icon_url
)
/datum/tgs_chat_embed/field/_interop_serialize()
return list(
"name" = name,
"value" = value,
"isInline" = is_inline
)
+283
View File
@@ -0,0 +1,283 @@
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
var/list/response = list()
if(error_message)
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
return response
/datum/tgs_api/v5/proc/ProcessTopicJson(json, check_access_identifier)
TGS_DEBUG_LOG("ProcessTopicJson(..., [check_access_identifier])")
var/list/result = ProcessRawTopic(json, check_access_identifier)
if(!result)
result = TopicResponse("Runtime error!")
else if(!length(result))
return "{}" // quirk of json_encode is an empty list returns "[]"
var/response_json = json_encode(result)
if(length(response_json) > DMAPI5_TOPIC_RESPONSE_LIMIT)
// cache response chunks and send the first
var/list/chunks = GenerateChunks(response_json, FALSE)
var/payload_id = chunks[1][DMAPI5_CHUNK][DMAPI5_CHUNK_PAYLOAD_ID]
var/cache_key = ResponseTopicChunkCacheKey(payload_id)
chunked_topics[cache_key] = chunks
response_json = json_encode(chunks[1])
return response_json
/datum/tgs_api/v5/proc/ProcessRawTopic(json, check_access_identifier)
TGS_DEBUG_LOG("ProcessRawTopic(..., [check_access_identifier])")
var/list/topic_parameters = json_decode(json)
if(!topic_parameters)
TGS_DEBUG_LOG("ProcessRawTopic: json_decode failed")
return TopicResponse("Invalid topic parameters json: [json]!");
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
if(check_access_identifier && their_sCK != access_identifier)
TGS_DEBUG_LOG("ProcessRawTopic: access identifier check failed")
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] or it does not match!")
var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]
if(!isnum(command))
TGS_DEBUG_LOG("ProcessRawTopic: command type check failed")
return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]!")
return ProcessTopicCommand(command, topic_parameters)
/datum/tgs_api/v5/proc/ResponseTopicChunkCacheKey(payload_id)
return "response[payload_id]"
/datum/tgs_api/v5/proc/ProcessTopicCommand(command, list/topic_parameters)
TGS_DEBUG_LOG("ProcessTopicCommand([command], ...)")
switch(command)
if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND)
intercepted_message_queue = list()
var/list/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
if(!result)
result = TopicResponse("Error running chat command!")
result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return result
if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION)
var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]
if(!istype(event_notification))
return TopicResponse("Invalid [DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]!")
var/event_type = event_notification[DMAPI5_EVENT_NOTIFICATION_TYPE]
if(!isnum(event_type))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_TYPE]!")
var/list/event_parameters = event_notification[DMAPI5_EVENT_NOTIFICATION_PARAMETERS]
if(event_parameters && !istype(event_parameters))
. = TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!")
else
var/list/response = TopicResponse()
. = response
if(event_handler != null)
var/list/event_call = list(event_type)
if(event_parameters)
event_call += event_parameters
intercepted_message_queue = list()
event_handler.HandleEvent(arglist(event_call))
response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
if (event_type == TGS_EVENT_WATCHDOG_DETACH)
detached = TRUE
chat_channels.Cut() // https://github.com/tgstation/tgstation-server/issues/1490
return
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
//the topic still completes, miraculously
//I honestly didn't believe byond could do it without exploding
if(!world.OpenPort(new_port))
return TopicResponse("Port change failed!")
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE)
var/new_reboot_mode = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]
if(!isnum(new_reboot_mode))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode)
reboot_mode = new_reboot_mode
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED)
var/new_instance_name = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]
if(!istext(new_instance_name))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_INSTANCE_RENAMED, new_instance_name)
instance_name = new_instance_name
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE)
TGS_DEBUG_LOG("ProcessTopicCommand: It's a chat update")
var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]
if(!istype(chat_update_json))
TGS_DEBUG_LOG("ProcessTopicCommand: failed \"[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]\" check")
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]!")
DecodeChannels(chat_update_json)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
server_port = new_port
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK)
if(event_handler && event_handler.receive_health_checks)
event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
detached = FALSE
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
var/error_message = null
if (new_port != null)
if (!isnum(new_port) || !(new_port > 0))
error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]"
else
server_port = new_port
var/new_version_string = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]
if (!istext(new_version_string))
if(error_message != null)
error_message += ", "
error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]"
else
var/datum/tgs_version/new_version = new(new_version_string)
if (event_handler)
event_handler.HandleEvent(TGS_EVENT_WATCHDOG_REATTACH, new_version)
version = new_version
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort()
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
var/list/chunk = topic_parameters[DMAPI5_CHUNK]
if(!istype(chunk))
return TopicResponse("Invalid [DMAPI5_CHUNK]!")
var/payload_id = chunk[DMAPI5_CHUNK_PAYLOAD_ID]
if(!isnum(payload_id))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!")
// Always updated the highest known payload ID
chunked_requests = max(chunked_requests, payload_id)
var/sequence_id = chunk[DMAPI5_CHUNK_SEQUENCE_ID]
if(!isnum(sequence_id))
return TopicResponse("[DMAPI5_CHUNK_SEQUENCE_ID] is not a number!")
var/total_chunks = chunk[DMAPI5_CHUNK_TOTAL]
if(!isnum(total_chunks))
return TopicResponse("[DMAPI5_CHUNK_TOTAL] is not a number!")
if(total_chunks == 0)
return TopicResponse("[DMAPI5_CHUNK_TOTAL] is zero!")
var/payload = chunk[DMAPI5_CHUNK_PAYLOAD]
if(!istext(payload))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD] is not text!")
var/cache_key = "request[payload_id]"
var/payloads = chunked_topics[cache_key]
if(!payloads)
payloads = new /list(total_chunks)
chunked_topics[cache_key] = payloads
if(total_chunks != length(payloads))
chunked_topics -= cache_key
return TopicResponse("Received differing total chunks for same [DMAPI5_CHUNK_PAYLOAD_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!")
var/pre_existing_chunk = payloads[sequence_id + 1]
if(pre_existing_chunk && pre_existing_chunk != payload)
chunked_topics -= cache_key
return TopicResponse("Received differing payload for same [DMAPI5_CHUNK_SEQUENCE_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!")
payloads[sequence_id + 1] = payload
var/list/missing_sequence_ids = list()
for(var/i in 1 to total_chunks)
if(!payloads[i])
missing_sequence_ids += i - 1
if(length(missing_sequence_ids))
return list(DMAPI5_MISSING_CHUNKS = missing_sequence_ids)
chunked_topics -= cache_key
var/full_json = jointext(payloads, "")
return ProcessRawTopic(full_json, FALSE)
if(DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK)
var/payload_id = topic_parameters[DMAPI5_CHUNK_PAYLOAD_ID]
if(!isnum(payload_id))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!")
// Always updated the highest known payload ID
chunked_requests = max(chunked_requests, payload_id)
var/list/missing_chunks = topic_parameters[DMAPI5_MISSING_CHUNKS]
if(!istype(missing_chunks) || !length(missing_chunks))
return TopicResponse("Missing or empty [DMAPI5_MISSING_CHUNKS]!")
var/sequence_id_to_send = missing_chunks[1]
if(!isnum(sequence_id_to_send))
return TopicResponse("[DMAPI5_MISSING_CHUNKS] contained a non-number!")
var/cache_key = ResponseTopicChunkCacheKey(payload_id)
var/list/chunks = chunked_topics[cache_key]
if(!chunks)
return TopicResponse("Unknown response chunk set: P[payload_id]!")
// sequence IDs in interop chunking are always zero indexed
var/chunk_to_send = chunks[sequence_id_to_send + 1]
if(!chunk_to_send)
return TopicResponse("Sequence ID [sequence_id_to_send] is not present in response chunk P[payload_id]!")
if(length(missing_chunks) == 1)
// sending last chunk, purge the cache
chunked_topics -= cache_key
return chunk_to_send
if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST)
var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]
if (!istext(message))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]")
TGS_WORLD_ANNOUNCE(message)
return TopicResponse()
return TopicResponse("Unknown command: [command]")
/datum/tgs_api/v5/proc/WorldBroadcast(message)
set waitfor = FALSE
TGS_WORLD_ANNOUNCE(message)
+118
View File
@@ -0,0 +1,118 @@
#undef DMAPI5_PARAM_SERVER_PORT
#undef DMAPI5_PARAM_ACCESS_IDENTIFIER
#undef DMAPI5_BRIDGE_DATA
#undef DMAPI5_TOPIC_DATA
#undef DMAPI5_BRIDGE_REQUEST_LIMIT
#undef DMAPI5_TOPIC_REQUEST_LIMIT
#undef DMAPI5_TOPIC_RESPONSE_LIMIT
#undef DMAPI5_BRIDGE_COMMAND_STARTUP
#undef DMAPI5_BRIDGE_COMMAND_PRIME
#undef DMAPI5_BRIDGE_COMMAND_REBOOT
#undef DMAPI5_BRIDGE_COMMAND_KILL
#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
#undef DMAPI5_BRIDGE_COMMAND_CHUNK
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
#undef DMAPI5_PARAMETER_TOPIC_PORT
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
#undef DMAPI5_CHUNK_TOTAL
#undef DMAPI5_CHUNK_SEQUENCE_ID
#undef DMAPI5_CHUNK_PAYLOAD_ID
#undef DMAPI5_MISSING_CHUNKS
#undef DMAPI5_RESPONSE_ERROR_MESSAGE
#undef DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE
#undef DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT
#undef DMAPI5_BRIDGE_PARAMETER_VERSION
#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
#undef DMAPI5_CHAT_MESSAGE_CHANNEL_IDS
#undef DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER
#undef DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION
#undef DMAPI5_RUNTIME_INFORMATION_SERVER_PORT
#undef DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY
#undef DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME
#undef DMAPI5_RUNTIME_INFORMATION_REVISION
#undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES
#undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL
#undef DMAPI5_RUNTIME_INFORMATION_VISIBILITY
#undef DMAPI5_CHAT_UPDATE_CHANNELS
#undef DMAPI5_TEST_MERGE_TIME_MERGED
#undef DMAPI5_TEST_MERGE_REVISION
#undef DMAPI5_TEST_MERGE_TITLE_AT_MERGE
#undef DMAPI5_TEST_MERGE_BODY_AT_MERGE
#undef DMAPI5_TEST_MERGE_URL
#undef DMAPI5_TEST_MERGE_AUTHOR
#undef DMAPI5_TEST_MERGE_NUMBER
#undef DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION
#undef DMAPI5_TEST_MERGE_COMMENT
#undef DMAPI5_CHAT_COMMAND_NAME
#undef DMAPI5_CHAT_COMMAND_PARAMS
#undef DMAPI5_CHAT_COMMAND_USER
#undef DMAPI5_EVENT_NOTIFICATION_TYPE
#undef DMAPI5_EVENT_NOTIFICATION_PARAMETERS
#undef DMAPI5_TOPIC_COMMAND_CHAT_COMMAND
#undef DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION
#undef DMAPI5_TOPIC_COMMAND_CHANGE_PORT
#undef DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE
#undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED
#undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE
#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
#undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
#undef DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION
#undef DMAPI5_TOPIC_PARAMETER_NEW_PORT
#undef DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE
#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
#undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA
#undef DMAPI5_REVISION_INFORMATION_TIMESTAMP
#undef DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA
#undef DMAPI5_CHAT_USER_ID
#undef DMAPI5_CHAT_USER_FRIENDLY_NAME
#undef DMAPI5_CHAT_USER_MENTION
#undef DMAPI5_CHAT_USER_CHANNEL
#undef DMAPI5_CHAT_CHANNEL_ID
#undef DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME
#undef DMAPI5_CHAT_CHANNEL_CONNECTION_NAME
#undef DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL
#undef DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL
#undef DMAPI5_CHAT_CHANNEL_TAG
#undef DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED
#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY