Skip to content

Commit 26ae565

Browse files
DaedalusDock-Servicestgstation-serverfrancinum
authored
Update TGS DMAPI (#1062)
Co-authored-by: tgstation-server <[email protected]> Co-authored-by: Gallyus <[email protected]>
1 parent 7ba5349 commit 26ae565

File tree

9 files changed

+119
-45
lines changed

9 files changed

+119
-45
lines changed

code/__DEFINES/tgs.dm

+73-27
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
// tgstation-server DMAPI
2+
// The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119.
23

3-
#define TGS_DMAPI_VERSION "7.1.2"
4+
#define TGS_DMAPI_VERSION "7.2.0"
45

56
// All functions and datums outside this document are subject to change with any version and should not be relied on.
67

78
// CONFIGURATION
89

9-
/// Create this define if you want to do TGS configuration outside of this file.
10+
/// Consumers SHOULD create this define if you want to do TGS configuration outside of this file.
1011
#ifndef TGS_EXTERNAL_CONFIGURATION
1112

12-
// Comment this out once you've filled in the below.
13+
// Consumers MUST comment this out once you've filled in the below and are not using [TGS_EXTERNAL_CONFIGURATION].
1314
#error TGS API unconfigured
1415

15-
// Uncomment this if you wish to allow the game to interact with TGS 3..
16+
// Consumers MUST uncomment this if you wish to allow the game to interact with TGS version 3.
1617
// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()().
1718
//#define TGS_V3_API
1819

@@ -52,7 +53,7 @@
5253

5354
#ifndef TGS_FILE2TEXT_NATIVE
5455
#ifdef file2text
55-
#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You can fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
56+
#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You SHOULD fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
5657
#endif
5758
#define TGS_FILE2TEXT_NATIVE file2text
5859
#endif
@@ -152,16 +153,17 @@
152153
//REQUIRED HOOKS
153154

154155
/**
155-
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
156+
* Consumers MUST call this somewhere in [/world/proc/New] that is always run. This function may sleep!
156157
*
157158
* * event_handler - Optional user defined [/datum/tgs_event_handler].
158159
* * 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].
160+
* * http_handler - Optional user defined [/datum/tgs_http_handler].
159161
*/
160-
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
162+
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler)
161163
return
162164

163165
/**
164-
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
166+
* Consumers MUST this when your initializations are complete and your game is ready to play before any player interactions happen.
165167
*
166168
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
167169
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
@@ -170,12 +172,10 @@
170172
/world/proc/TgsInitializationComplete()
171173
return
172174

173-
/// Put this at the start of [/world/proc/Topic].
175+
/// Consumers MUST run this macro at the start of [/world/proc/Topic].
174176
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
175177

176-
/**
177-
* Call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
178-
*/
178+
/// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
179179
/world/proc/TgsReboot()
180180
return
181181

@@ -269,7 +269,7 @@
269269
/// The [/datum/tgs_chat_channel] the user was from.
270270
var/datum/tgs_chat_channel/channel
271271

272-
/// User definable handler for TGS events.
272+
/// User definable handler for TGS events This abstract version SHOULD be overridden to be used.
273273
/datum/tgs_event_handler
274274
/// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events.
275275
var/receive_health_checks = FALSE
@@ -283,7 +283,41 @@
283283
set waitfor = FALSE
284284
return
285285

286-
/// User definable chat command.
286+
/// User definable handler for HTTP calls. This abstract version MUST be overridden to be used.
287+
/datum/tgs_http_handler
288+
289+
/**
290+
* User definable callback for executing HTTP GET requests.
291+
* MUST perform BYOND sleeps while the request is in flight.
292+
* MUST return a [/datum/tgs_http_result].
293+
* SHOULD log its own errors
294+
*
295+
* url - The full URL to execute the GET request for including query parameters.
296+
*/
297+
/datum/tgs_http_handler/proc/PerformGet(url)
298+
CRASH("[type]/PerformGet not implemented!")
299+
300+
/// Result of a [/datum/tgs_http_handler] call. MUST NOT be overridden.
301+
/datum/tgs_http_result
302+
/// HTTP response as text
303+
var/response_text
304+
/// Boolean request success flag. Set for any 2XX response code.
305+
var/success
306+
307+
/**
308+
* Create a [/datum/tgs_http_result].
309+
*
310+
* * response_text - HTTP response as text. Must be provided in New().
311+
* * success - Boolean request success flag. Set for any 2XX response code. Must be provided in New().
312+
*/
313+
/datum/tgs_http_result/New(response_text, success)
314+
if(response_text && !istext(response_text))
315+
CRASH("response_text was not text!")
316+
317+
src.response_text = response_text
318+
src.success = success
319+
320+
/// User definable chat command. This abstract version MUST be overridden to be used.
287321
/datum/tgs_chat_command
288322
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`.
289323
var/name = ""
@@ -296,21 +330,27 @@
296330

297331
/**
298332
* Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with.
333+
* MUST be implemented
299334
*
300-
* sender - The [/datum/tgs_chat_user] who issued the command.
301-
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
335+
* * sender - The [/datum/tgs_chat_user] who issued the command.
336+
* * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
302337
*/
303338
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
304339
CRASH("[type] has no implementation for Run()")
305340

306-
/// User definable chat message.
341+
/// User definable chat message. MUST NOT be overridden.
307342
/datum/tgs_message_content
308-
/// The tring content of the message. Must be provided in New().
343+
/// The string content of the message. Must be provided in New().
309344
var/text
310345

311346
/// The [/datum/tgs_chat_embed] to embed in the message. Not supported on all chat providers.
312347
var/datum/tgs_chat_embed/structure/embed
313348

349+
/**
350+
* Create a [/datum/tgs_message_content].
351+
*
352+
* * text - The string content of the message.
353+
*/
314354
/datum/tgs_message_content/New(text)
315355
..()
316356
if(!istext(text))
@@ -319,7 +359,7 @@
319359

320360
src.text = text
321361

322-
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/channel#embed-object-embed-structure for details.
362+
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/message#embed-object for details.
323363
/datum/tgs_chat_embed/structure
324364
var/title
325365
var/description
@@ -331,13 +371,13 @@
331371
/// Colour must be #AARRGGBB or #RRGGBB hex string.
332372
var/colour
333373

334-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
374+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-image-structure for details.
335375
var/datum/tgs_chat_embed/media/image
336376

337-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure for details.
377+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-thumbnail-structure for details.
338378
var/datum/tgs_chat_embed/media/thumbnail
339379

340-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
380+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure for details.
341381
var/datum/tgs_chat_embed/media/video
342382

343383
var/datum/tgs_chat_embed/footer/footer
@@ -346,58 +386,64 @@
346386

347387
var/list/datum/tgs_chat_embed/field/fields
348388

349-
/// Common datum for similar discord embed medias.
389+
/// Common datum for similar Discord embed medias.
350390
/datum/tgs_chat_embed/media
351391
/// Must be set in New().
352392
var/url
353393
var/width
354394
var/height
355395
var/proxy_url
356396

397+
/// Create a [/datum/tgs_chat_embed].
357398
/datum/tgs_chat_embed/media/New(url)
358399
..()
359400
if(!istext(url))
360401
CRASH("[/datum/tgs_chat_embed/media] created with no url!")
361402

362403
src.url = url
363404

364-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure for details.
405+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-footer-structure for details.
365406
/datum/tgs_chat_embed/footer
366407
/// Must be set in New().
367408
var/text
368409
var/icon_url
369410
var/proxy_icon_url
370411

412+
/// Create a [/datum/tgs_chat_embed/footer].
371413
/datum/tgs_chat_embed/footer/New(text)
372414
..()
373415
if(!istext(text))
374416
CRASH("[/datum/tgs_chat_embed/footer] created with no text!")
375417

376418
src.text = text
377419

378-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure for details.
420+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-provider-structure for details.
379421
/datum/tgs_chat_embed/provider
380422
var/name
381423
var/url
382424

383-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure for details. Must have name set in New().
425+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-author-structure for details. Must have name set in New().
384426
/datum/tgs_chat_embed/provider/author
385427
var/icon_url
386428
var/proxy_icon_url
387429

430+
/// Create a [/datum/tgs_chat_embed/footer].
388431
/datum/tgs_chat_embed/provider/author/New(name)
389432
..()
390433
if(!istext(name))
391434
CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!")
392435

393436
src.name = name
394437

395-
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure for details. Must have name and value set in New().
438+
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure for details.
396439
/datum/tgs_chat_embed/field
440+
/// Must be set in New().
397441
var/name
442+
/// Must be set in New().
398443
var/value
399444
var/is_inline
400445

446+
/// Create a [/datum/tgs_chat_embed/field].
401447
/datum/tgs_chat_embed/field/New(name, value)
402448
..()
403449
if(!istext(name))

code/modules/tgs/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DMAPI Internals
22

3-
This folder should be placed on it's own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified.
3+
This folder should be placed on its own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified.
44

55
- [includes.dm](./includes.dm) is the file that should be included by DM code, it handles including the rest.
66
- The [core](./core) folder includes all code not directly part of any API version.

code/modules/tgs/core/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This folder contains all DMAPI code not directly involved in an API.
44

55
- [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
6+
- [byond_world_export.dm](./byond_world_export.dm) contains the default `/datum/tgs_http_handler` implementation which uses `world.Export()`.
67
- [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.
78
- [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement.
89
- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition
9-
-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/datum/tgs_http_handler/byond_world_export
2+
3+
/datum/tgs_http_handler/byond_world_export/PerformGet(url)
4+
// This is an infinite sleep until we get a response
5+
var/export_response = world.Export(url)
6+
TGS_DEBUG_LOG("byond_world_export: Export complete")
7+
8+
if(!export_response)
9+
TGS_ERROR_LOG("byond_world_export: Failed request: [url]")
10+
return new /datum/tgs_http_result(null, FALSE)
11+
12+
var/content = export_response["CONTENT"]
13+
if(!content)
14+
TGS_ERROR_LOG("byond_world_export: Failed request, missing content!")
15+
return new /datum/tgs_http_result(null, FALSE)
16+
17+
var/response_json = TGS_FILE2TEXT_NATIVE(content)
18+
if(!response_json)
19+
TGS_ERROR_LOG("byond_world_export: Failed request, failed to load content!")
20+
return new /datum/tgs_http_result(null, FALSE)
21+
22+
return new /datum/tgs_http_result(response_json, TRUE)

code/modules/tgs/core/core.dm

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
1+
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler = null)
22
var/current_api = TGS_READ_GLOBAL(tgs)
33
if(current_api)
44
TGS_ERROR_LOG("API datum already set (\ref[current_api] ([current_api]))! Was TgsNew() called more than once?")
@@ -55,7 +55,10 @@
5555
TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]")
5656
event_handler = null
5757

58-
var/datum/tgs_api/new_api = new api_datum(event_handler, version)
58+
if(!http_handler)
59+
http_handler = new /datum/tgs_http_handler/byond_world_export
60+
61+
var/datum/tgs_api/new_api = new api_datum(event_handler, version, http_handler)
5962

6063
TGS_WRITE_GLOBAL(tgs, new_api)
6164

code/modules/tgs/core/datum.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
66

77
var/list/warned_deprecated_command_runs
88

9-
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version)
9+
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
1010
..()
1111
src.event_handler = event_handler
1212
src.version = version

code/modules/tgs/includes.dm

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "core\_definitions.dm"
2+
#include "core\byond_world_export.dm"
23
#include "core\core.dm"
34
#include "core\datum.dm"
45
#include "core\tgs_version.dm"

code/modules/tgs/v5/api.dm

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@
3131

3232
var/detached = FALSE
3333

34-
/datum/tgs_api/v5/New()
34+
var/datum/tgs_http_handler/http_handler
35+
36+
/datum/tgs_api/v5/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
3537
. = ..()
3638
interop_version = version
39+
src.http_handler = http_handler
3740
TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
3841

3942
/datum/tgs_api/v5/ApiVersion()
@@ -50,7 +53,9 @@
5053
version = null // we want this to be the TGS version, not the interop version
5154

5255
// sleep once to prevent an issue where world.Export on the first tick can hang indefinitely
56+
TGS_DEBUG_LOG("Starting Export bug prevention sleep tick. time:[world.time] sleep_offline:[world.sleep_offline]")
5357
sleep(world.tick_lag)
58+
TGS_DEBUG_LOG("Export bug prevention sleep complete")
5459

5560
var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
5661
if(!istype(bridge_response))

code/modules/tgs/v5/bridge.dm

+9-12
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,24 @@
7878
WaitForReattach(FALSE)
7979

8080
TGS_DEBUG_LOG("Bridge request start")
81-
// This is an infinite sleep until we get a response
82-
var/export_response = world.Export(bridge_request)
81+
var/datum/tgs_http_result/result = http_handler.PerformGet(bridge_request)
8382
TGS_DEBUG_LOG("Bridge request complete")
8483

85-
if(!export_response)
86-
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
84+
if(isnull(result))
85+
TGS_ERROR_LOG("Failed bridge request, handler returned null!")
8786
return
8887

89-
var/content = export_response["CONTENT"]
90-
if(!content)
91-
TGS_ERROR_LOG("Failed bridge request, missing content!")
88+
if(!istype(result) || result.type != /datum/tgs_http_result)
89+
TGS_ERROR_LOG("Failed bridge request, handler returned non-[/datum/tgs_http_result]!")
9290
return
9391

94-
var/response_json = TGS_FILE2TEXT_NATIVE(content)
95-
if(!response_json)
96-
TGS_ERROR_LOG("Failed bridge request, failed to load content!")
92+
if(!result.success)
93+
TGS_DEBUG_LOG("Failed bridge request, HTTP request failed!")
9794
return
9895

99-
var/list/bridge_response = json_decode(response_json)
96+
var/list/bridge_response = json_decode(result.response_text)
10097
if(!bridge_response)
101-
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
98+
TGS_ERROR_LOG("Failed bridge request, bad json: [result.response_text]")
10299
return
103100

104101
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]

0 commit comments

Comments
 (0)