Updating TGS/DMAPI to version 4

This commit is contained in:
Repede
2019-03-11 21:31:45 -04:00
parent a4b9c31bdf
commit eb06799e86
16 changed files with 1317 additions and 321 deletions

263
code/__defines/_tgs.dm Normal file
View File

@@ -0,0 +1,263 @@
//tgstation-server DMAPI
//All functions and datums outside this document are subject to change with any version and should not be relied on
//CONFIGURATION
//create this define if you want to do configuration outside of this file
#ifndef TGS_EXTERNAL_CONFIGURATION
//Comment this out once you've filled in the below
//#error TGS API unconfigured
//Uncomment this if you wish to allow the game to interact with TGS 3
//This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()()
//#define TGS_V3_API
//Required interfaces (fill in with your codebase equivalent):
//create a global variable named `Name` and set it to `Value`
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
//Read the value in the global variable `Name`
#define TGS_READ_GLOBAL(Name) GLOB.##Name
//Set the value in the global variable `Name` to `Value`
#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
//Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game use of DD -> TGS capabilities
#define TGS_PROTECT_DATUM(Path)
//Display an announcement `message` from the server to all players
#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "<span class='boldannounce'>[html_encode(##message)]</span>")
//Notify current in-game administrators of a string `event`
#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
//Write an info `message` to a server log
#define TGS_INFO_LOG(message) log_world("SERVICE: [##message]")
//Write an error `message` to a server log
#define TGS_ERROR_LOG(message)
//Get the number of connected /clients
#define TGS_CLIENT_COUNT player_list.len
//Sends a message to connected game chats
#define SERVER_TOOLS_CHAT_BROADCAST(message) world.TgsChatBroadcast(message)
//Sends a message to connected admin chats
#define SERVER_TOOLS_RELAY_BROADCAST(message) world.TgsTargetedChatBroadcast(message)
#endif
//EVENT CODES
#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port
#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums
//See the descriptions for these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs
#define TGS_EVENT_REPO_RESET_ORIGIN 0
#define TGS_EVENT_REPO_CHECKOUT 1
#define TGS_EVENT_REPO_FETCH 2
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
#define TGS_EVENT_BYOND_INSTALL_START 5
#define TGS_EVENT_BYOND_INSTALL_FAIL 6
#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7
#define TGS_EVENT_COMPILE_START 8
#define TGS_EVENT_COMPILE_CANCELLED 9
#define TGS_EVENT_COMPILE_FAILURE 10
#define TGS_EVENT_COMPILE_COMPLETE 11
#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12
#define TGS_EVENT_REPO_MERGE_CONFLICT 13
//OTHER ENUMS
#define TGS_REBOOT_MODE_NORMAL 0
#define TGS_REBOOT_MODE_SHUTDOWN 1
#define TGS_REBOOT_MODE_RESTART 2
#define TGS_SECURITY_TRUSTED 0
#define TGS_SECURITY_SAFE 1
#define TGS_SECURITY_ULTRASAFE 2
//REQUIRED HOOKS
//Call this somewhere in /world/New() that is always run
//event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels
//minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
return
//Call this when your initializations are complete and your game is ready to play before any player interactions happen
//This may use world.sleep_offline to make this happen so ensure no changes are made to it while this call is running
//Most importantly, before this point, note that any static files or directories may be in use by another server. Your code should account for this
//This function should not be called before ..() in /world/New()
/world/proc/TgsInitializationComplete()
return
//Put this at the start of /world/Topic()
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
//Call this at the beginning of world/Reboot(reason)
/world/proc/TgsReboot()
return
//DATUM DEFINITIONS
//unless otherwise specified all datums defined here should be considered read-only, warranty void if written
//represents git revision information about the current world build
/datum/tgs_revision_information
var/commit //full sha of compiled commit
var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch
//represents a version of tgstation-server
/datum/tgs_version
var/suite //The suite version, can be >=3
//this group of variables can be null to represent a wild card
var/major //The major version
var/minor //The minor version
var/patch //The patch version
var/raw_parameter //The unparsed parameter
var/deprefixed_parameter //The version only bit of raw_parameter
//if the tgs_version is a wildcard version
/datum/tgs_version/proc/Wildcard()
return
//represents a merge of a GitHub pull request
/datum/tgs_revision_information/test_merge
var/number //pull request number
var/title //pull request title
var/body //pull request body
var/author //pull request github author
var/url //link to pull request html
var/pull_request_commit //commit of the pull request when it was merged
var/time_merged //timestamp of when the merge commit for the pull request was created
var/comment //optional comment left by the one who initiated the test merge
//represents a connected chat channel
/datum/tgs_chat_channel
var/id //internal channel representation
var/friendly_name //user friendly channel name
var/connection_name //the name of the configured chat connection
var/is_admin_channel //if the server operator has marked this channel for game admins only
var/is_private_channel //if this is a private chat channel
var/custom_tag //user defined string associated with channel
//represents a chat user
/datum/tgs_chat_user
var/id //Internal user representation, requires channel to be unique
var/friendly_name //The user's public name
var/mention //The text to use to ping this user in a message
var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from
//user definable callback for handling events
//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
/datum/tgs_chat_command
var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command
var/help_text = "" //help text for this command
var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins
//override to implement command
//sender: The tgs_chat_user who send to command
//params: The trimmed string following the command name
//The return value will be stringified and sent to the appropriate chat
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
CRASH("[type] has no implementation for Run()")
//FUNCTIONS
//Returns the respective supported /datum/tgs_version of the API
/world/proc/TgsMaximumAPIVersion()
return
/world/proc/TgsMinimumAPIVersion()
return
//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise
//No function below this succeeds if it returns FALSE
/world/proc/TgsAvailable()
return
//Gets the current /datum/tgs_version of the server tools running the server
/world/proc/TgsVersion()
return
//Gets the name of the TGS instance running the game
/world/proc/TgsInstanceName()
return
//Get the current `/datum/tgs_revision_information`
/world/proc/TgsRevision()
return
//Get the current BYOND security level
/world/proc/TgsSecurityLevel()
return
//Gets a list of active `/datum/tgs_revision_information/test_merge`s
/world/proc/TgsTestMerges()
return
//Forces a hard reboot of BYOND by ending the process
//unlike del(world) clients will try to reconnect
//If the service has not requested a shutdown, the next server will take over
/world/proc/TgsEndProcess()
return
//Gets a list of connected tgs_chat_channel
/world/proc/TgsChatChannelInfo()
return
//Sends a message to connected game chats
//message: The message to send
//channels: optional channels to limit the broadcast to
/world/proc/TgsChatBroadcast(message, list/channels)
return
//Send a message to non-admin connected chats
//message: The message to send
//admin_only: If TRUE, message will instead be sent to only admin connected chats
/world/proc/TgsTargetedChatBroadcast(message, admin_only)
return
//Send a private message to a specific user
//message: The message to send
//user: The /datum/tgs_chat_user to send to
/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
return
/*
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.
*/

View File

@@ -1,101 +0,0 @@
// /tg/station 13 server tools API
#define SERVICE_API_VERSION_STRING "3.2.0.3"
//CONFIGURATION
//use this define if you want to do configuration outside of this file
//Required hooks:
//Put this somewhere in /world/New() that is always run
#define SERVER_TOOLS_ON_NEW ServiceInit()
//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified
#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return
//Put at the beginning of world/Reboot(reason)
#define SERVER_TOOLS_ON_REBOOT ServiceReboot()
//Optional callable functions:
//Returns the string version of the API
#define SERVER_TOOLS_API_VERSION ServiceAPIVersion()
//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise
//No function below this succeed if this is FALSE
#define SERVER_TOOLS_PRESENT RunningService()
//Gets the current version of the service running the server
#define SERVER_TOOLS_VERSION ServiceVersion()
//Forces a hard reboot of BYOND by ending the process
//unlike del(world) clients will try to reconnect
//If the service has not requested a shutdown, the world will reboot shortly after
#define SERVER_TOOLS_REBOOT_BYOND(silent) world.ServiceEndProcess(silent)
/*
Gets the list of any testmerged github pull requests
"[PR Number]" => list(
"title" -> PR title
"commit" -> Full hash of commit merged
"author" -> Github username of the author of the PR
)
*/
#define SERVER_TOOLS_PR_LIST GetTestMerges()
//Sends a message to connected game chats
#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message)
//Sends a message to connected admin chats
#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message)
//IMPLEMENTATION
#define REBOOT_MODE_NORMAL 0
#define REBOOT_MODE_HARD 1
#define REBOOT_MODE_SHUTDOWN 2
#define SERVICE_WORLD_PARAM "server_service"
#define SERVICE_VERSION_PARAM "server_service_version"
#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_JSON_PARAM_HELPTEXT "help_text"
#define SERVICE_JSON_PARAM_ADMINONLY "admin_only"
#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters"
#define SERVICE_REQUEST_KILL_PROCESS "killme"
#define SERVICE_REQUEST_KILL_PROCESS_SILENT "killmesilent"
#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"
/*
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.
*/

View File

@@ -1,11 +1,11 @@
/world/New()
SERVER_TOOLS_ON_NEW
return ..()
..()
TgsNew()
/world/Reboot(var/reason)
SERVER_TOOLS_ON_REBOOT
return ..(reason)
/world/Reboot()
TgsReboot()
..()
/world/Topic(T, addr, master, key)
SERVER_TOOLS_ON_TOPIC
return ..(T, addr, master, key)
/world/Topic()
TGS_TOPIC
..()

View File

@@ -1,72 +0,0 @@
/datum/server_tools_command
var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command
var/help_text = "" //help text for this command
var/required_parameters = 0 //number of parameters required for this command
var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins
//override to implement command
//sender is the display name of who sent the command
//params is the trimmed string following the command name
/datum/server_tools_command/proc/Run(sender, params)
CRASH("[type] has no implementation for Run()")
/world/proc/ListServiceCustomCommands(warnings_only)
if(!warnings_only)
. = list()
var/list/command_name_types = list()
var/list/warned_command_names = warnings_only ? list() : null
for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command)
var/datum/server_tools_command/stc = I
var/command_name = initial(stc.name)
var/static/list/warned_server_tools_names = list()
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
if(warnings_only && !warned_command_names[command_name])
SERVER_TOOLS_LOG("WARNING: Custom command [command_name] cannot 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)
SERVER_TOOLS_LOG("WARNING: 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 = initial(stc.required_parameters))
/world/proc/HandleServiceCustomCommand(command, sender, params)
var/static/list/cached_custom_server_tools_commands
if(!cached_custom_server_tools_commands)
cached_custom_server_tools_commands = list()
for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command)
var/datum/server_tools_command/stc = I
cached_custom_server_tools_commands[lowertext(initial(stc.name))] = stc
var/command_type = cached_custom_server_tools_commands[command]
if(!command_type)
return FALSE
var/datum/server_tools_command/stc = new command_type
return stc.Run(sender, params) || TRUE
/*
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.
*/

View File

@@ -1,131 +0,0 @@
SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(reboot_mode, REBOOT_MODE_NORMAL)
SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE)
/proc/GetTestMerges()
if(RunningService(TRUE) && fexists(SERVICE_PR_TEST_JSON))
. = json_decode(file2text(SERVICE_PR_TEST_JSON))
if(.)
return
return list()
/world/proc/ServiceInit()
if(!RunningService(TRUE))
return
ListServiceCustomCommands(TRUE)
ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE)
/proc/RunningService(skip_compat_check = FALSE)
if(!skip_compat_check && !SERVER_TOOLS_READ_GLOBAL(server_tools_api_compatible))
return FALSE
. = world.params[SERVICE_WORLD_PARAM] != null
if(. && world.system_type != MS_WINDOWS)
SERVER_TOOLS_LOG("Warning: Server tools world parameter detected but not running on Windows. Aborting initialization!")
return FALSE
/proc/ServiceVersion()
if(RunningService(TRUE))
return world.params[SERVICE_VERSION_PARAM]
/proc/ServiceAPIVersion()
return SERVICE_API_VERSION_STRING
/world/proc/ExportService(command, skip_compat_check = FALSE)
. = FALSE
if(!RunningService(skip_compat_check))
return
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
var/instance = params[SERVICE_INSTANCE_PARAM]
if(!instance)
instance = "TG Station Server" //maybe just upgraded
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval
return TRUE
/world/proc/ChatBroadcast(message)
ExportService("[SERVICE_REQUEST_IRC_BROADCAST] [message]")
/world/proc/AdminBroadcast(message)
ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [message]")
/world/proc/ServiceEndProcess(silent = FALSE)
SERVER_TOOLS_LOG("Sending shutdown request!");
sleep(world.tick_lag) //flush the buffers
ExportService(silent ? SERVICE_REQUEST_KILL_PROCESS_SILENT : SERVICE_REQUEST_KILL_PROCESS)
//called at the exact moment the world is supposed to reboot
/world/proc/ServiceReboot()
switch(SERVER_TOOLS_READ_GLOBAL(reboot_mode))
if(REBOOT_MODE_HARD)
SERVER_TOOLS_WORLD_ANNOUNCE("gay")//Hard reboot triggered, you will automatically reconnect...")
ServiceEndProcess()
if(REBOOT_MODE_SHUTDOWN)
SERVER_TOOLS_WORLD_ANNOUNCE("The server is shutting down...")
ServiceEndProcess()
else
ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know
/world/proc/ServiceCommand(list/params)
var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
if(!their_sCK || !RunningService(TRUE))
return FALSE //continue world/Topic
var/sCK = world.params[SERVICE_WORLD_PARAM]
if(their_sCK != sCK)
return "Invalid comms key!";
var/command = params[SERVICE_CMD_PARAM_COMMAND]
if(!command)
return "No command!"
switch(command)
if(SERVICE_CMD_API_COMPATIBLE)
SERVER_TOOLS_WRITE_GLOBAL(server_tools_api_compatible, TRUE)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_HARD_REBOOT)
if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_HARD)
SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_HARD)
SERVER_TOOLS_LOG("Hard reboot requested by service")
SERVER_TOOLS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by service.")
if(SERVICE_CMD_GRACEFUL_SHUTDOWN)
if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_SHUTDOWN)
SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_SHUTDOWN)
SERVER_TOOLS_LOG("Shutdown requested by service")
message_admins("The world will shutdown at the end of the game. Requested by service.")
if(SERVICE_CMD_WORLD_ANNOUNCE)
var/msg = params["message"]
if(!istext(msg) || !msg)
return "No message set!"
SERVER_TOOLS_WORLD_ANNOUNCE(msg)
return SERVICE_RETURN_SUCCESS
if(SERVICE_CMD_PLAYER_COUNT)
return "[SERVER_TOOLS_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]"
/*
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.
*/

View File

@@ -0,0 +1,2 @@
#define TGS_UNIMPLEMENTED "___unimplemented"
#define TGS_VERSION_PARAMETER "server_service_version"

View File

@@ -0,0 +1,164 @@
/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("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?")
return
#ifdef TGS_V3_API
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 TGS 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!")
#else
switch(version.major)
if(2)
api_datum = /datum/tgs_api/v3210
#endif
if(4)
switch(version.major)
if(0)
api_datum = /datum/tgs_api/v4
var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion();
if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
TGS_ERROR_LOG("Detected unknown 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 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]")
var/datum/tgs_api/new_api = new api_datum(version)
TGS_WRITE_GLOBAL(tgs, new_api)
var/result = new_api.OnWorldNew(event_handler, 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("4.0.x.x")
/world/TgsMinimumAPIVersion()
return new /datum/tgs_version("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()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.version
/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)
api.SecurityLevel()
/*
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.
*/

View File

@@ -0,0 +1,82 @@
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
/datum/tgs_api
var/datum/tgs_version/version
/datum/tgs_api/New(datum/tgs_version/version)
. = ..()
src.version = version
/datum/tgs_api/latest
parent_type = /datum/tgs_api/v4
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
/datum/tgs_api/proc/SecurityLevel()
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.
*/

View File

@@ -0,0 +1,22 @@
/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)
major = text2num(version_bits[2])
if(version_bits.len > 2)
minor = text2num(version_bits[3])
if(version_bits.len == 4)
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 major == null || minor == null || patch == null

View File

@@ -0,0 +1,10 @@
#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"

View File

@@ -0,0 +1,253 @@
#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, minimum_required_security_level) //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
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()
/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, datum/tgs_chat_user/user)
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
/*
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.
*/

View File

@@ -0,0 +1,78 @@
#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.
*/

View File

@@ -0,0 +1,342 @@
#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/datum/tgs_event_handler/event_handler
var/export_lock = FALSE
var/list/last_interop_response
/datum/tgs_api/v4/ApiVersion()
return "4.0.0.0"
/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level)
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]"))
del(world)
security_level = cached_json["securityLevel"]
chat_channels_json_path = cached_json["chatChannelsJson"]
chat_commands_json_path = cached_json["chatCommandsJson"]
src.event_handler = event_handler
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.time_merged = 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.pull_request_commit = entry["pullRequestRevision"]
tm.comment = entry["comment"]
cached_test_merges += tm
return TRUE
/datum/tgs_api/v4/OnInitializationComplete()
Export(TGS4_COMM_SERVER_PRIMED)
var/tgs4_secret_sleep_offline_sauce = 24051994
var/old_sleep_offline = world.sleep_offline
world.sleep_offline = tgs4_secret_sleep_offline_sauce
sleep(1)
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_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]")
del(world)
//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]")
del(world)
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]")
del(world)
if(new_port != world.port && !world.OpenPort(new_port))
TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]")
del(world)
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
/datum/tgs_api/v4/EndProcess()
Export(TGS4_COMM_END_PROCESS)
/datum/tgs_api/v4/Revision()
return cached_revision
/datum/tgs_api/v4/ChatBroadcast(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 = list("message" = message, "channelIds" = ids)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Export(TGS4_COMM_CHAT, message)
/datum/tgs_api/v4/ChatTargetedBroadcast(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 = list("message" = message, "channelIds" = channels)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Export(TGS4_COMM_CHAT, message)
/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user)
message = list("message" = message, "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
/*
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.
*/

View File

@@ -0,0 +1,69 @@
/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 += 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/result = sc.Run(u, params)
if(result == null)
result = ""
return result
return "Unknown command: [command]!"
/*
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.
*/

View File

@@ -1,20 +1,30 @@
/datum/server_tools_command/status
var/global/client_count = 0
/client/New()
..()
++global.client_count
/client/Del()
..()
--global.client_count
/datum/tgs_chat_command/status
name = "status" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command
help_text = "Will broadcast the current player count and other round information" //help text for this command
required_parameters = 0 //number of parameters required for this command
admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins
/datum/server_tools_command/status/Run(sender, params)
/datum/tgs_chat_command/status/Run(datum/tgs_chat_user/sender, params)
return "```PLAYERCOUNT: [SERVER_TOOLS_CLIENT_COUNT] ROUND DURATION: [roundduration2text()]```"
// - FAX
/datum/server_tools_command/readfax
/datum/tgs_chat_command/readfax
name = "readfax"
help_text = "Reads a fax with specified faxid"
required_parameters = 1
admin_only = TRUE
/datum/server_tools_command/readfax/Run(sender, params)
/datum/tgs_chat_command/readfax/Run(datum/tgs_chat_user/sender, params)
var/list/all_params = splittext(params, " ")
var/faxid = all_params[1]
var/faxmsg = return_file_text("[config.fax_export_dir]/fax_[faxid].html")

View File

@@ -27,6 +27,7 @@
#include "code\__defines\_globals.dm"
#include "code\__defines\_planes+layers.dm"
#include "code\__defines\_planes+layers_vr.dm"
#include "code\__defines\_tgs.dm"
#include "code\__defines\_tick.dm"
#include "code\__defines\admin.dm"
#include "code\__defines\admin_vr.dm"
@@ -63,7 +64,6 @@
#include "code\__defines\research.dm"
#include "code\__defines\roguemining_vr.dm"
#include "code\__defines\server_tools.config.dm"
#include "code\__defines\server_tools.dm"
#include "code\__defines\sound.dm"
#include "code\__defines\species_languages.dm"
#include "code\__defines\species_languages_vr.dm"
@@ -1695,9 +1695,14 @@
#include "code\modules\detectivework\tools\storage.dm"
#include "code\modules\detectivework\tools\swabs.dm"
#include "code\modules\detectivework\tools\uvlight.dm"
#include "code\modules\DMAPI\st_commands.dm"
#include "code\modules\DMAPI\st_interface.dm"
#include "code\modules\DMAPI\yawnDMAPI.dm"
#include "code\modules\DMAPI\tgs\includes.dm"
#include "code\modules\DMAPI\tgs\core\_definitions.dm"
#include "code\modules\DMAPI\tgs\core\core.dm"
#include "code\modules\DMAPI\tgs\core\datum.dm"
#include "code\modules\DMAPI\tgs\core\tgs_version.dm"
#include "code\modules\DMAPI\tgs\v4\api.dm"
#include "code\modules\DMAPI\tgs\v4\commands.dm"
#include "code\modules\economy\Accounts.dm"
#include "code\modules\economy\Accounts_DB.dm"
#include "code\modules\economy\ATM.dm"