Skip to content

Commit aa08995

Browse files
committed
Co-authored-by: Crystalic <BlackCrystalic@users.noreply.github.com>
1 parent b8d1ea7 commit aa08995

6 files changed

Lines changed: 360 additions & 1 deletion

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
GLOBAL_LIST_EMPTY(topic_tokens)
2+
GLOBAL_PROTECT(topic_tokens)
3+
4+
GLOBAL_LIST_EMPTY(topic_commands)
5+
GLOBAL_PROTECT(topic_commands)
6+
7+
SUBSYSTEM_DEF(topic)
8+
name = "Topic"
9+
init_stage = INITSTAGE_FIRST
10+
flags = SS_NO_FIRE
11+
12+
/datum/controller/subsystem/topic/Initialize(timeofday)
13+
// Initialize topic datums
14+
var/list/anonymous_functions = list()
15+
for(var/path in subtypesof(/datum/world_topic))
16+
var/datum/world_topic/T = new path()
17+
if(T.anonymous)
18+
anonymous_functions[T.key] = TRUE
19+
GLOB.topic_commands[T.key] = T
20+
21+
// Setup the anonymous access token
22+
GLOB.topic_tokens["anonymous"] = anonymous_functions
23+
// Parse and setup authed tokens from config
24+
var/list/tokens = CONFIG_GET(keyed_list/topic_tokens)
25+
for(var/token in tokens)
26+
var/list/keys = list()
27+
if(tokens[token] == "all")
28+
for(var/key in GLOB.topic_commands)
29+
keys[key] = TRUE
30+
else
31+
for(var/key in splittext(tokens[token], ","))
32+
keys[trim(key)] = TRUE
33+
// Grant access to anonymous topic calls (version, authed functions etc.) by default
34+
keys |= anonymous_functions
35+
GLOB.topic_tokens[token] = keys
36+
37+
return ..()
38+
39+
/datum/config_entry/number/topic_max_size
40+
config_entry_value = 5000
41+
protection = CONFIG_ENTRY_HIDDEN|CONFIG_ENTRY_LOCKED
42+
43+
/datum/config_entry/keyed_list/topic_tokens
44+
key_mode = KEY_MODE_TEXT
45+
value_mode = VALUE_MODE_TEXT
46+
protection = CONFIG_ENTRY_HIDDEN|CONFIG_ENTRY_LOCKED
47+
48+
/datum/config_entry/keyed_list/topic_tokens/ValidateListEntry(key_name, key_value)
49+
return key_value != "topic_token" && ..()

code/datums/world_topic.dm

Lines changed: 238 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SETUP
2-
2+
/* OLD
33
/proc/TopicHandlers()
44
. = list()
55
var/list/all_handlers = subtypesof(/datum/world_topic)
@@ -316,3 +316,240 @@
316316
317317
message_admins(span_adminnotice("Incoming cross-sector newscaster article by [author_key] in channel [channel_name]."))
318318
GLOB.news_network.submit_article(msg, author, channel_name)
319+
*/
320+
321+
322+
323+
324+
325+
/datum/world_topic
326+
/// query key
327+
var/key
328+
329+
/// can be used with anonymous authentication
330+
var/anonymous = FALSE
331+
332+
var/list/required_params = list()
333+
var/statuscode
334+
var/response
335+
var/data
336+
337+
/datum/world_topic/proc/CheckParams(list/params)
338+
var/list/missing_params = list()
339+
var/errorcount = 0
340+
341+
for(var/param in required_params)
342+
if(!params[param])
343+
errorcount++
344+
missing_params += param
345+
346+
if(errorcount)
347+
statuscode = 400
348+
response = "Bad Request - Missing parameters"
349+
data = missing_params
350+
return errorcount
351+
352+
/datum/world_topic/proc/Run(list/input)
353+
// Always returns true; actual details in statuscode, response and data variables
354+
return TRUE
355+
356+
// API INFO TOPICS
357+
358+
/datum/world_topic/api_get_authed_functions
359+
key = "api_get_authed_functions"
360+
anonymous = TRUE
361+
362+
/datum/world_topic/api_get_authed_functions/Run(list/input)
363+
. = ..()
364+
var/list/functions = GLOB.topic_tokens[input["auth"]]
365+
if(functions)
366+
statuscode = 200
367+
response = "Authorized functions retrieved"
368+
data = functions
369+
else
370+
statuscode = 401
371+
response = "Unauthorized - No functions found"
372+
data = null
373+
374+
// TOPICS
375+
376+
// If you modify the protocol for this, update tools/Tgstation.PRAnnouncer
377+
/datum/world_topic/pr_announce
378+
key = "announce"
379+
anonymous = TRUE
380+
var/static/list/PRcounts = list() //PR id -> number of times announced this round
381+
382+
/datum/world_topic/pr_announce/Run(list/input)
383+
. = ..()
384+
var/list/payload = json_decode(input["payload"])
385+
var/id = "[payload["pull_request"]["id"]]"
386+
if(!PRcounts[id])
387+
PRcounts[id] = 1
388+
else
389+
++PRcounts[id]
390+
if(PRcounts[id] > CONFIG_GET(number/pr_announcements_per_round))
391+
return
392+
393+
var/final_composed = span_announce("PR: [input[key]]")
394+
for(var/client/C in GLOB.clients)
395+
C.AnnouncePR(final_composed)
396+
397+
statuscode = 200
398+
response = "Received"
399+
400+
/datum/world_topic/ping
401+
key = "ping"
402+
anonymous = TRUE
403+
404+
/datum/world_topic/ping/Run(list/input)
405+
. = ..()
406+
statuscode = 200
407+
response = "Pong!"
408+
data = length(GLOB.clients)
409+
410+
/datum/world_topic/status
411+
key = "status"
412+
anonymous = TRUE
413+
414+
/datum/world_topic/status/Run(list/input)
415+
. = ..()
416+
417+
data = list()
418+
419+
data["version"] = GLOB.game_version
420+
data["respawn"] = config ? !!CONFIG_GET(flag/allow_respawn) : FALSE // show respawn as true regardless of "respawn as char" or "free respawn"
421+
data["enter"] = !LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS)
422+
data["ai"] = CONFIG_GET(flag/allow_ai)
423+
data["host"] = world.host ? world.host : null
424+
425+
data["round_id"] = GLOB.round_id
426+
427+
data["map_name"] = SSmapping.current_map?.map_name || "Loading..."
428+
429+
data["players"] = length(GLOB.clients)
430+
431+
data["identifier"] = CONFIG_GET(string/serversqlname)
432+
433+
434+
var/public_address = CONFIG_GET(string/public_address)
435+
if(public_address)
436+
data["public_address"] = public_address
437+
438+
data["revision"] = GLOB.revdata.commit
439+
data["revision_date"] = GLOB.revdata.date
440+
data["hub"] = GLOB.hub_visibility
441+
442+
data["security_level"] = SSsecurity_level.get_current_level_as_text()
443+
data["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
444+
445+
//Time dilation stats.
446+
data["time_dilation_current"] = SStime_track.time_dilation_current
447+
data["time_dilation_avg"] = SStime_track.time_dilation_avg
448+
data["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow
449+
data["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast
450+
451+
data["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0
452+
data["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0
453+
data["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0
454+
data["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases
455+
data["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE
456+
data["interviews"] = CONFIG_GET(flag/panic_bunker_interview) || FALSE
457+
if(SSshuttle?.emergency)
458+
data["shuttle_mode"] = SSshuttle.emergency.mode
459+
// Shuttle status, see /__DEFINES/stat.dm
460+
data["shuttle_timer"] = SSshuttle.emergency.timeLeft()
461+
// Shuttle timer, in seconds
462+
463+
statuscode = 200
464+
response = "Status retrieved"
465+
466+
/datum/world_topic/status/authed
467+
key = "status_authed"
468+
anonymous = FALSE
469+
470+
/datum/world_topic/status/authed/Run(list/input)
471+
. = ..()
472+
473+
var/list/adm = get_admin_counts()
474+
var/list/presentmins = adm["present"]
475+
var/list/afkmins = adm["afk"]
476+
data["admins"] = length(presentmins) + length(afkmins)
477+
data["gamestate"] = SSticker.current_state
478+
479+
data["active_players"] = get_active_player_count()
480+
481+
data["mcpu"] = world.map_cpu
482+
data["cpu"] = world.cpu
483+
484+
GLOBAL_LIST_EMPTY(bot_event_sending_que)
485+
GLOBAL_LIST_EMPTY(bot_ooc_sending_que)
486+
GLOBAL_LIST_EMPTY(bot_asay_sending_que)
487+
488+
/datum/world_topic/receive_info
489+
key = "receive_info"
490+
491+
/datum/world_topic/receive_info/Run(list/input)
492+
data = list()
493+
if(!length(GLOB.bot_event_sending_que) && !length(GLOB.bot_ooc_sending_que) && !length(GLOB.bot_asay_sending_que))
494+
statuscode = 501
495+
response = "No events pool."
496+
return
497+
498+
//Yeah, we can use /datum/http_request, but nuh... it's less fun.
499+
data["events"] = GLOB.bot_event_sending_que
500+
data["ooc"] = GLOB.bot_ooc_sending_que
501+
data["admin"] = GLOB.bot_asay_sending_que
502+
GLOB.bot_event_sending_que = list()
503+
GLOB.bot_ooc_sending_que = list()
504+
GLOB.bot_asay_sending_que = list()
505+
statuscode = 200
506+
response = "Events sent."
507+
508+
/datum/world_topic/send_info
509+
key = "send_info"
510+
required_params = list("data")
511+
512+
/datum/world_topic/send_info/Run(list/input)
513+
data = list()
514+
515+
var/list/bot_data = input["data"]
516+
if(!istype(bot_data) || !length(bot_data))
517+
statuscode = 403
518+
response = "Wrong data"
519+
return
520+
521+
if(bot_data["ooc"])
522+
for(var/list/data in bot_data["ooc"])
523+
var/msg = sanitize(data["message"])
524+
for(var/client/C in GLOB.clients)
525+
if(C.prefs.chat_toggles & CHAT_OOC)
526+
to_chat(C, "<span class='ooc'><span class='prefix'>DISCORD OOC:</span> <EM>[data["author"]]:</EM> <span class='message linkify'>[msg]</span></span>")
527+
528+
if(bot_data["admin"])
529+
for(var/list/data in bot_data["admin"])
530+
to_chat(GLOB.admins, "<span class='adminsay'><span class='prefix'>DISCORD ADMIN:</span> <EM>[data["author"]]</EM>: <span class='message linkify'>[sanitize(data["message"])]</span></span>", confidential = TRUE)
531+
532+
statuscode = 200
533+
response = "Events received."
534+
535+
/datum/world_topic/delay
536+
key = "set_delay"
537+
required_params = list("delay")
538+
539+
/datum/world_topic/delay/Run(list/input)
540+
. = ..()
541+
542+
if(SSticker.timeLeft < 0 && input["delay"])
543+
statuscode = 501
544+
response = "Delay already set to same state."
545+
return
546+
547+
SSticker.timeLeft = input["delay"] ? -1 : 300
548+
message_admins(span_notice("[input["source"]] ([input["addr"]]) [SSticker.timeLeft < 0 ? "delayed the round start" : "has made the round start normally"]."))
549+
to_chat(world, span_notice("The game start has been [SSticker.timeLeft < 0 ? "delayed" : "continued"]."))
550+
if(SSticker.timeLeft < 0)
551+
statuscode = 200
552+
response = "Delay set."
553+
else
554+
statuscode = 200
555+
response = "Delay removed."

code/game/world.dm

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ GLOBAL_VAR(restart_counter)
250250
world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them.
251251
#endif
252252

253+
/* bot backdoor
253254
/world/Topic(T, addr, master, key)
254255
TGS_TOPIC //redirect to server tools if necessary
255256
@@ -270,6 +271,74 @@ GLOBAL_VAR(restart_counter)
270271
271272
handler = new handler()
272273
return handler.TryRun(input)
274+
*/
275+
//REPLACED
276+
/world/Topic(T, addr, master, key)
277+
TGS_TOPIC //redirect to server tools if necessary
278+
279+
var/list/response = list()
280+
281+
if(length(T) > CONFIG_GET(number/topic_max_size))
282+
response["statuscode"] = 413
283+
response["response"] = "Payload too large"
284+
return json_encode(response)
285+
286+
var/logging = CONFIG_GET(flag/log_world_topic)
287+
var/topic_decoded = rustg_url_decode(T)
288+
if(!rustg_json_is_valid(topic_decoded))
289+
if(logging)
290+
log_topic("(NON-JSON) \"[topic_decoded]\", from:[addr], master:[master], key:[key]")
291+
// Fallback check for spacestation13.com requests
292+
if(topic_decoded == "status")
293+
return list2params(list("players" = length(GLOB.clients)))
294+
response["statuscode"] = 400
295+
response["response"] = "Bad Request - Invalid JSON format"
296+
return json_encode(response)
297+
298+
var/list/params = json_decode(topic_decoded)
299+
params["addr"] = addr
300+
var/query = params["query"]
301+
var/auth = params["auth"]
302+
var/source = params["source"]
303+
304+
if(logging)
305+
var/list/censored_params = params.Copy()
306+
censored_params["auth"] = "***[copytext(params["auth"], -4)]"
307+
log_topic("\"[json_encode(censored_params)]\", from:[addr], master:[master], auth:[censored_params["auth"]], key:[key], source:[source]")
308+
309+
if(!source)
310+
response["statuscode"] = 400
311+
response["response"] = "Bad Request - No source specified"
312+
return json_encode(response)
313+
314+
if(!query)
315+
response["statuscode"] = 400
316+
response["response"] = "Bad Request - No endpoint specified"
317+
return json_encode(response)
318+
319+
if(!LAZYACCESS(GLOB.topic_tokens["[auth]"], "[query]"))
320+
response["statuscode"] = 401
321+
response["response"] = "Unauthorized - Bad auth"
322+
return json_encode(response)
323+
324+
var/datum/world_topic/command = GLOB.topic_commands["[query]"]
325+
if(!command)
326+
response["statuscode"] = 501
327+
response["response"] = "Not Implemented"
328+
return json_encode(response)
329+
330+
if(command.CheckParams(params))
331+
response["statuscode"] = command.statuscode
332+
response["response"] = command.response
333+
response["data"] = command.data
334+
return json_encode(response)
335+
else
336+
command.Run(params)
337+
response["statuscode"] = command.statuscode
338+
response["response"] = command.response
339+
response["data"] = command.data
340+
return json_encode(response)
341+
//YOUR MOM
273342

274343
/world/proc/AnnouncePR(announcement, list/payload)
275344
var/static/list/PRcounts = list() //PR id -> number of times announced this round

code/modules/admin/verbs/adminsay.dm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ADMIN_VERB(cmd_admin_say, R_NONE, "ASay", "Send a message to other admins", ADMI
1818

1919
user.mob.log_talk(message, LOG_ASAY)
2020
message = keywords_lookup(message)
21+
GLOB.bot_asay_sending_que += list(list("author" = user.ckey, "message" = message, "rank" = join_admin_ranks(user.holder.ranks)))
2122
var/asay_color = user.prefs.read_preference(/datum/preference/color/asay_color)
2223
var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && asay_color) ? "<font color=[asay_color]>" : "<font color='[DEFAULT_ASAY_COLOR]'>"
2324
message = "[span_adminsay("[span_prefix("ADMIN:")] <EM>[key_name_admin(user)]</EM> [ADMIN_FLW(user.mob)]: [custom_asay_color]<span class='message linkify'>[message]")]</span>[custom_asay_color ? "</font>":null]"

code/modules/client/verbs/ooc.dm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
5656
if(!msg)
5757
return
5858

59+
GLOB.bot_ooc_sending_que += list(list("author" = holder && holder.fakekey ? holder.fakekey : key, "message" = msg))
60+
5961
msg = emoji_parse(msg)
6062

6163
if(SSticker.HasRoundStarted() && ((msg[1] in list(".",";",":","#")) || findtext_char(msg, "say", 1, 5)))

0 commit comments

Comments
 (0)