* Initial Commit - Need to test this on TGS

* Bump to V5 (needs testing)

* CRLF --> LF

* Tweaks

* Latest cyberboss request

* Forgot to prime TGS

* Integrated TG #52572 / TGS #1087

* Fixes var(space) not being var/

* DMAPI 5.2.4

* Bump to 5.2.5
This commit is contained in:
AffectedArc07
2020-10-26 21:54:31 +00:00
committed by GitHub
parent 9e8932afac
commit 8fc730e68c
24 changed files with 1928 additions and 2 deletions
+11
View File
@@ -0,0 +1,11 @@
#define TGS_EXTERNAL_CONFIGURATION
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
#define TGS_READ_GLOBAL(Name) GLOB.##Name
#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "<span class='boldannounce'>[html_encode(##message)]</span>")
#define TGS_INFO_LOG(message) log_world("\[TGS] Info: [##message]")
#define TGS_WARNING_LOG(message) log_world("\[TGS] Warn: [##message]")
#define TGS_ERROR_LOG(message) log_world("\[TGS] Error: [##message]")
#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
#define TGS_CLIENT_COUNT GLOB.clients.len
#define TGS_PROTECT_DATUM(Path)
+328
View File
@@ -0,0 +1,328 @@
// AA Note
// yes I know this file has procs in it, but TGS is very picky with how the fuck its files are laid out
// dont @ me
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "5.2.5"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
// EVENT CODES
/// Before a reboot mode change, extras parameters are the current and new reboot mode enums
#define TGS_EVENT_REBOOT_MODE_CHANGE -1
/// Before a port change is about to happen, extra parameters is new port
#define TGS_EVENT_PORT_SWAP -2
/// Before the instance is renamed, extra parameter is the new name
#define TGS_EVENT_INSTANCE_RENAMED -3
/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server
#define TGS_EVENT_WATCHDOG_REATTACH -4
/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA
#define TGS_EVENT_REPO_RESET_ORIGIN 0
/// When the repository performs a checkout. Parameters: Checkout git object
#define TGS_EVENT_REPO_CHECKOUT 1
/// When the repository performs a fetch operation. No parameters
#define TGS_EVENT_REPO_FETCH 2
/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND
#define TGS_EVENT_BYOND_INSTALL_START 5
/// When a BYOND install operation fails. Parameters: Error message
#define TGS_EVENT_BYOND_INSTALL_FAIL 6
/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND
#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7
/// When the compiler starts running. Parameters: Game directory path, origin commit SHA
#define TGS_EVENT_COMPILE_START 8
/// When a compile is cancelled. No parameters
#define TGS_EVENT_COMPILE_CANCELLED 9
/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation
#define TGS_EVENT_COMPILE_FAILURE 10
/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path
#define TGS_EVENT_COMPILE_COMPLETE 11
/// When an automatic update for the current instance begins. No parameters
#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12
/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference
#define TGS_EVENT_REPO_MERGE_CONFLICT 13
/// When a deployment completes. No Parameters
#define TGS_EVENT_DEPLOYMENT_COMPLETE 14
/// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters.
#define TGS_EVENT_WATCHDOG_SHUTDOWN 15
/// Before the watchdog detaches for a TGS update/restart. No parameters.
#define TGS_EVENT_WATCHDOG_DETACH 16
// OTHER ENUMS
/// The server will reboot normally.
#define TGS_REBOOT_MODE_NORMAL 0
/// The server will stop running on reboot.
#define TGS_REBOOT_MODE_SHUTDOWN 1
/// The watchdog will restart on reboot.
#define TGS_REBOOT_MODE_RESTART 2
/// DreamDaemon Trusted security level.
#define TGS_SECURITY_TRUSTED 0
/// DreamDaemon Safe security level.
#define TGS_SECURITY_SAFE 1
/// DreamDaemon Ultrasafe security level.
#define TGS_SECURITY_ULTRASAFE 2
//REQUIRED HOOKS
/**
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
*
* * event_handler - Optional user defined [/datum/tgs_event_handler].
* * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
*/
/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/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
* 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/proc/New].
*/
/world/proc/TgsInitializationComplete()
return
/// Put this at the start of [/world/proc/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/proc/Reboot].
*/
/world/proc/TgsReboot()
return
// DATUM DEFINITIONS
// All datums defined here should be considered read-only
/// Represents git revision information.
/datum/tgs_revision_information
/// Full SHA of the commit.
var/commit
/// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch.
var/origin_commit
/// Represents a version.
/datum/tgs_version
/// The suite/major version number
var/suite
// This group of variables can be null to represent a wild card
/// The minor version number. null for wildcards
var/minor
/// The patch version number. null for wildcards
var/patch
/// Legacy version number. Generally null
var/deprecated_patch
/// Unparsed string value
var/raw_parameter
/// String value minus prefix
var/deprefixed_parameter
/**
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
*/
/datum/tgs_version/proc/Wildcard()
return
/**
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
*
* other_version - The [/datum/tgs_version] to compare against.
*/
/datum/tgs_version/proc/Equals(datum/tgs_version/other_version)
return
/// Represents a merge of a GitHub pull request.
/datum/tgs_revision_information/test_merge
/// The pull request number.
var/number
/// The pull request title when it was merged.
var/title
/// The pull request body when it was merged.
var/body
/// The GitHub username of the pull request's author.
var/author
/// An http URL to the pull request.
var/url
/// The SHA of the pull request when that was merged.
var/pull_request_commit
/// ISO 8601 timestamp of when the pull request was merged.
var/time_merged
/// (Nullable) Comment left by the TGS user who initiated the merge..
var/comment
/// Represents a connected chat channel.
/datum/tgs_chat_channel
/// TGS internal channel ID.
var/id
/// User friendly name of the channel.
var/friendly_name
/// Name of the chat connection. This is the IRC server address or the Discord guild.
var/connection_name
/// [TRUE]/[FALSE] based on if the server operator has marked this channel for game admins only.
var/is_admin_channel
/// [TRUE]/[FALSE] if the channel is a private message channel for a [/datum/tgs_chat_user].
var/is_private_channel
/// Tag string associated with the channel in TGS
var/custom_tag
// Represents a chat user
/datum/tgs_chat_user
/// TGS internal user ID.
var/id
// The user's display name.
var/friendly_name
// The string to use to ping this user in a message.
var/mention
/// The [/datum/tgs_chat_channel] the user was from
var/datum/tgs_chat_channel/channel
/**
* User definable callback for handling TGS events.
*
* event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
*/
/datum/tgs_event_handler/proc/HandleEvent(event_code, ...)
set waitfor = FALSE
return
/// User definable chat command
/datum/tgs_chat_command
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`
var/name = ""
/// The help text displayed for this command
var/help_text = ""
/// If this command should be available to game administrators only
var/admin_only = FALSE
/**
* Process command activation. Should return a string to respond to the issuer with.
*
* sender - The [/datum/tgs_chat_user] who issued the command.
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
*/
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
CRASH("[type] has no implementation for Run()")
// API FUNCTIONS
/// Returns the maximum supported [/datum/tgs_version] of the DMAPI.
/world/proc/TgsMaximumAPIVersion()
return
/// Returns the minimum supported [/datum/tgs_version] of the DMAPI.
/world/proc/TgsMinimumAPIVersion()
return
/**
* Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
*/
/world/proc/TgsAvailable()
return
// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
/**
* Forces a hard reboot of DreamDaemon by ending the process.
*
* Unlike del(world) clients will try to reconnect.
* If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
*/
/world/proc/TgsEndProcess()
return
/**
* Send a message to connected chats.
*
* message - The string to send.
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
*/
/world/proc/TgsTargetedChatBroadcast(message, admin_only = FALSE)
return
/**
* Send a private message to a specific user.
*
* message - The string to send.
* user: The [/datum/tgs_chat_user] to PM.
*/
/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
return
// The following functions will sleep if a call to TgsNew() is sleeping
/**
* Send a message to connected chats that are flagged as game-related in TGS.
*
* message - The string to send.
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
*/
/world/proc/TgsChatBroadcast(message, list/channels = null)
return
/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise.
/world/proc/TgsVersion()
return
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise.
/world/proc/TgsApiVersion()
return
/// Returns the name of the TGS instance running the game if TGS is present, null otherwise.
/world/proc/TgsInstanceName()
return
/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise.
/world/proc/TgsRevision()
return
/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise.
/world/proc/TgsSecurityLevel()
return
/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise.
/world/proc/TgsTestMerges()
return
/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise.
/world/proc/TgsChatChannelInfo()
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.
*/
+4 -1
View File
@@ -162,12 +162,15 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Please don't stuff random bullshit here,
// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
/datum/controller/master/Initialize(delay, init_sss)
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
if(delay)
sleep(delay)
if(tgs_prime)
world.TgsInitializationComplete()
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
+39
View File
@@ -0,0 +1,39 @@
/datum/tgs_event_handler/impl
var/datum/timedevent/reattach_timer
/datum/tgs_event_handler/impl/HandleEvent(event_code, ...)
switch(event_code)
if(TGS_EVENT_REBOOT_MODE_CHANGE)
var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server")
var/old_reboot_mode = args[2]
var/new_reboot_mode = args[3]
message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]")
if(TGS_EVENT_PORT_SWAP)
message_admins("TGS: Changing port from [world.port] to [args[2]]")
if(TGS_EVENT_INSTANCE_RENAMED)
message_admins("TGS: Instance renamed to from [world.TgsInstanceName()] to [args[2]]")
if(TGS_EVENT_COMPILE_START)
message_admins("TGS: Deployment started, new game version incoming...")
if(TGS_EVENT_COMPILE_CANCELLED)
message_admins("TGS: Deployment cancelled!")
if(TGS_EVENT_COMPILE_FAILURE)
message_admins("TGS: Deployment failed!")
if(TGS_EVENT_DEPLOYMENT_COMPLETE)
message_admins("TGS: Deployment complete!")
to_chat(world, "<span class='boldannounce'>Server updated, changes will be applied on the next round...</span>")
if(TGS_EVENT_WATCHDOG_DETACH)
message_admins("TGS restarting...")
reattach_timer = addtimer(CALLBACK(src, .proc/LateOnReattach), 1 MINUTES)
if(TGS_EVENT_WATCHDOG_REATTACH)
var/datum/tgs_version/old_version = world.TgsVersion()
var/datum/tgs_version/new_version = args[2]
if(!old_version.Equals(new_version))
to_chat(world, "<span class='boldannounce'>TGS updated to v[new_version.deprefixed_parameter]</span>")
else
message_admins("TGS: Back online")
if(reattach_timer)
deltimer(reattach_timer)
reattach_timer = null
/datum/tgs_event_handler/impl/proc/LateOnReattach()
message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?")
+5 -1
View File
@@ -7,6 +7,7 @@ GLOBAL_LIST_INIT(map_transition_config, MAP_TRANSITION_CONFIG)
// Setup all log paths and stamp them with startups
SetupLogs()
enable_debugger() // Enable the extools debugger
TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) // creates a new TGS object
log_world("World loaded at [time_stamp()]")
log_world("[GLOB.vars.len - GLOB.gvars_datum_in_built_vars.len] global variables")
connectDB() // This NEEDS TO HAPPEN EARLY. I CANNOT STRESS THIS ENOUGH!!!!!!! -aa
@@ -34,7 +35,8 @@ GLOBAL_LIST_INIT(map_transition_config, MAP_TRANSITION_CONFIG)
. = ..()
Master.Initialize(10, FALSE)
Master.Initialize(10, FALSE, TRUE)
#ifdef UNIT_TESTS
HandleTestRun()
@@ -67,6 +69,7 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_ip, "0.0.0.0")
GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday)
/world/Topic(T, addr, master, key)
TGS_TOPIC
log_misc("WORLD/TOPIC: \"[T]\", from:[addr], master:[master], key:[key]")
var/list/input = params2list(T)
@@ -288,6 +291,7 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday)
return "Bad Key"
/world/Reboot(var/reason, var/feedback_c, var/feedback_r, var/time)
TgsReboot()
if(reason == 1) //special reboot, do none of the normal stuff
if(usr)
if(!check_rights(R_SERVER))
+24
View File
@@ -0,0 +1,24 @@
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.
+13
View File
@@ -0,0 +1,13 @@
# DMAPI Internals
This folder should be placed on it's own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified.
- [includes.dm](./includes.dm) is the file that should be included by DM code, it handles including the rest.
- The [core](./core) folder includes all code not directly part of any API version.
- The other versioned folders contain code for the different DMAPI versions.
- [v3210](./v3210) contains the final TGS3 API.
- [v4](./v4) is the legacy DMAPI 4 (Used in TGS 4.0.X versions).
- [v5](./v5) is the current DMAPI version used by TGS4 >=4.1.
- [LICENSE](./LICENSE) is the MIT license for the DMAPI.
APIs communicate with TGS in two ways. All versions implement TGS -> DM communication using /world/Topic. DM -> TGS communication, called the bridge method, is different for each version.
+8
View File
@@ -0,0 +1,8 @@
# Core DMAPI functions
This folder contains all DMAPI code not directly involved in an API.
- [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
- [core.dm](./core.dm) contains the implementations of the `/world/proc/TgsXXX()` procs. Many map directly to the `/datum/tgs_api` functions. It also contains the /datum selection and setup code.
- [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement.
- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition
+2
View File
@@ -0,0 +1,2 @@
#define TGS_UNIMPLEMENTED "___unimplemented"
#define TGS_VERSION_PARAMETER "server_service_version"
+156
View File
@@ -0,0 +1,156 @@
/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 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]")
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/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()
+57
View File
@@ -0,0 +1,57 @@
TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
/datum/tgs_api
var/datum/tgs_version/version
var/datum/tgs_event_handler/event_handler
/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/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
+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
+17
View File
@@ -0,0 +1,17 @@
#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\commands.dm"
#include "v5\undef.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.
+228
View File
@@ -0,0 +1,228 @@
#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 new /datum/tgs_version("3.2.1.1")
/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(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 = 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)
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
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)
var/datum/tgs_version/api_version = ApiVersion()
TGS_ERROR_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(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
+52
View File
@@ -0,0 +1,52 @@
#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
// Discord hack, fix the mention if it's only numbers (fuck you IRC trolls)
var/regex/discord_id_regex = regex(@"[0-9]+")
if(discord_id_regex.Find(sender))
sender = "<@[sender]>"
user.mention = sender
return stc.Run(user, params) || TRUE
+6
View File
@@ -0,0 +1,6 @@
# DMAPI V4
This DMAPI implements bridge requests using file output which TGS monitors for. It has a safe mode restriction.
- [api.dm](./api.dm) contains the bulk of the API code.
- [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
+309
View File
@@ -0,0 +1,309 @@
#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]"))
del(world)
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.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)
/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.Copy()
/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
+41
View File
@@ -0,0 +1,41 @@
/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]!"
+98
View File
@@ -0,0 +1,98 @@
#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_COMMAND_PORT_UPDATE 0
#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_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
#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_TEXT "text"
#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_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_HEARTBEAT 7
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#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_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage"
#define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses"
#define DMAPI5_REVISION_INFORMATION_COMMIT_SHA "commitSha"
#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_CUSTOM_CHAT_COMMAND_NAME "name"
#define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText"
#define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly"
+354
View File
@@ -0,0 +1,354 @@
/datum/tgs_api/v5
var/server_port
var/access_identifier
var/instance_name
var/security_level
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
var/list/intercepted_message_queue
var/list/custom_commands
var/list/test_merges
var/datum/tgs_revision_information/revision
var/list/chat_channels
var/initialized = FALSE
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version("5.2.5")
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
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
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()))
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...")
del(world)
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION])
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
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.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]
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!")
tm.time_merged = text2num(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.pull_request_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/RequireInitialBridgeResponse()
while(!version)
sleep(1)
/datum/tgs_api/v5/OnInitializationComplete()
Bridge(DMAPI5_BRIDGE_COMMAND_PRIME)
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
var/list/response = list()
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
return json_encode(response)
/datum/tgs_api/v5/OnTopic(T)
if(!initialized)
return FALSE //continue world/Topic
var/list/params = params2list(T)
var/json = params[DMAPI5_TOPIC_DATA]
if(!json)
return FALSE
var/list/topic_parameters = json_decode(json)
if(!topic_parameters)
return TopicResponse("Invalid topic parameters json!");
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
if(their_sCK != access_identifier)
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!");
var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]
if(!isnum(command))
return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] from: [json]!")
switch(command)
if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND)
var/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
if(!result)
result = TopicResponse("Error running chat command!")
return result
if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION)
intercepted_message_queue = list()
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))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!")
var/list/event_call = list(event_type)
if(event_parameters)
event_call += event_parameters
if(event_handler != null)
event_handler.HandleEvent(arglist(event_call))
var/list/response = list()
response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return json_encode(response)
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)
var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]
if(!istype(chat_update_json))
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_HEARTBEAT)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
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
return json_encode(list(DMAPI5_RESPONSE_ERROR_MESSAGE = error_message, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
return TopicResponse("Unknown command: [command]")
/datum/tgs_api/v5/proc/Bridge(command, list/data)
if(!data)
data = list()
data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command
data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier
var/json = json_encode(data)
var/encoded_json = url_encode(json)
// This is an infinite sleep until we get a response
var/export_response = world.Export("http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]")
if(!export_response)
TGS_ERROR_LOG("Failed export request: [json]")
return
var/response_json = file2text(export_response["CONTENT"])
if(!response_json)
TGS_ERROR_LOG("Failed export request, missing content!")
return
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed export request, bad json: [response_json]")
return
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
if(error)
TGS_ERROR_LOG("Failed export request, bad request: [error]")
return
return bridge_response
/datum/tgs_api/v5/OnReboot()
var/list/result = Bridge(DMAPI5_BRIDGE_COMMAND_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[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
/datum/tgs_api/v5/ChatBroadcast(message, 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
message = list(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = ids)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
/datum/tgs_api/v5/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(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = channels)
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
/datum/tgs_api/v5/ChatPrivateMessage(message, datum/tgs_chat_user/user)
message = list(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = list(user.channel.id))
if(intercepted_message_queue)
intercepted_message_queue += list(message)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
/datum/tgs_api/v5/ChatChannelInfo()
RequireInitialBridgeResponse()
return chat_channels.Copy()
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
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]
return channel
/datum/tgs_api/v5/SecurityLevel()
RequireInitialBridgeResponse()
return security_level
+40
View File
@@ -0,0 +1,40 @@
/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
var/command_name = stc.name
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
TGS_WARNING_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_WARNING_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/text_response = sc.Run(u, params)
var/list/topic_response = list()
if(!istext(text_response))
TGS_ERROR_LOG("Custom command [command] should return a string! Got: \"[text_response]\"")
text_response = null
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = text_response
return json_encode(topic_response)
return TopicResponse("Unknown custom chat command: [command]!")
+98
View File
@@ -0,0 +1,98 @@
#undef DMAPI5_PARAM_SERVER_PORT
#undef DMAPI5_PARAM_ACCESS_IDENTIFIER
#undef DMAPI5_BRIDGE_DATA
#undef DMAPI5_TOPIC_DATA
#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
#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_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
#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_TEXT
#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_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_HEARTBEAT
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
#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_RESPONSE_COMMAND_RESPONSE_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
#undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA
#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_CUSTOM_CHAT_COMMAND_NAME
#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
+4
View File
@@ -18,6 +18,7 @@
#include "code\__DEFINES\_globals.dm"
#include "code\__DEFINES\_readme.dm"
#include "code\__DEFINES\_spacemandmm.dm"
#include "code\__DEFINES\_tgs_defines.dm"
#include "code\__DEFINES\_tick.dm"
#include "code\__DEFINES\access.dm"
#include "code\__DEFINES\admin.dm"
@@ -74,6 +75,7 @@
#include "code\__DEFINES\station_goals.dm"
#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\subsystems.dm"
#include "code\__DEFINES\tgs.dm"
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\tools.dm"
#include "code\__DEFINES\typeids.dm"
@@ -295,6 +297,7 @@
#include "code\datums\spell.dm"
#include "code\datums\statclick.dm"
#include "code\datums\supplypacks.dm"
#include "code\datums\tgs_event_handler.dm"
#include "code\datums\uplink_item.dm"
#include "code\datums\vision_override.dm"
#include "code\datums\cache\air_alarm.dm"
@@ -2438,6 +2441,7 @@
#include "code\modules\telesci\rcs.dm"
#include "code\modules\telesci\telepad.dm"
#include "code\modules\telesci\telesci_computer.dm"
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
#include "code\modules\tgui\tgui.dm"