// SETUP /proc/TopicHandlers() . = list() var/list/all_handlers = subtypesof(/datum/world_topic) for(var/I in all_handlers) var/datum/world_topic/WT = I var/keyword = initial(WT.keyword) if(!keyword) warning("[WT] has no keyword! Ignoring...") continue var/existing_path = .[keyword] if(existing_path) warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") else if(keyword == "key") warning("[WT] has keyword 'key'! Ignoring...") else .[keyword] = WT // DATUM /datum/world_topic var/keyword var/log = TRUE var/key_valid /// If the comms.txt config key is required. If you flip this to false, ensure the code is correct and the query you receive is legit. var/require_comms_key = TRUE /datum/world_topic/proc/TryRun(list/input) key_valid = (CONFIG_GET(string/comms_key) == input["key"]) && CONFIG_GET(string/comms_key) && input["key"] input -= "key" if(require_comms_key && !key_valid) . = "Bad Key" if (input["format"] == "json") . = list("error" = .) else . = Run(input) if (input["format"] == "json") . = json_encode(.) else if(islist(.)) . = list2params(.) /datum/world_topic/proc/Run(list/input) CRASH("Run() not implemented for [type]!") /** TOPICS * These are the handlers for world.Export() -> World.Topic() server communication. * Double check to ensure any calls are correct and the query is legit. * World.Topic() exploits can be very devastating since these can be called via a normal player connection without a client. * https://secure.byond.com/docs/ref/index.html#/world/proc/Topic */ // If you modify the protocol for this, update tools/Tgstation.PRAnnouncer /datum/world_topic/ping keyword = "ping" log = FALSE require_comms_key = FALSE /datum/world_topic/ping/Run(list/input) . = 0 for (var/client/C in GLOB.clients) ++. /datum/world_topic/playing keyword = "playing" log = FALSE require_comms_key = FALSE /datum/world_topic/playing/Run(list/input) return GLOB.player_list.len // If you modify the protocol for this, update tools/Tgstation.PRAnnouncer /datum/world_topic/pr_announce keyword = "announce" var/static/list/PRcounts = list() //PR id -> number of times announced this round /datum/world_topic/pr_announce/Run(list/input) var/list/payload = json_decode(input["payload"]) var/id = "[payload["pull_request"]["id"]]" if(!PRcounts[id]) PRcounts[id] = 1 else ++PRcounts[id] if(PRcounts[id] > CONFIG_GET(number/pr_announcements_per_round)) return var/final_composed = span_announce("PR: [input[keyword]]") for(var/client/C in GLOB.clients) C.AnnouncePR(final_composed) /datum/world_topic/ahelp_relay keyword = "Ahelp" /datum/world_topic/ahelp_relay/Run(list/input) relay_msg_admins(span_adminnotice("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]")) /datum/world_topic/comms_console keyword = "Comms_Console" var/list/timers /datum/world_topic/comms_console/Run(list/input) // Reject comms messages from other servers that are not on our configured network, // if this has been configured. (See CROSS_COMMS_NETWORK in comms.txt) var/configured_network = CONFIG_GET(string/cross_comms_network) if (configured_network && configured_network != input["network"]) return // We can't add the timer without the timer ID, but we can't get the timer ID without the timer! // To solve this, we just use a list that we mutate later. var/list/data = list("input" = input) // Did we have to pass the soft filter on our origin server? Passed as a boolean value. var/soft_filter_passed = !!input["is_filtered"] var/timer_id = addtimer(CALLBACK(src, PROC_REF(receive_cross_comms_message), data), soft_filter_passed ? EXTENDED_CROSS_SECTOR_CANCEL_TIME : CROSS_SECTOR_CANCEL_TIME, TIMER_STOPPABLE) data["timer_id"] = timer_id LAZYADD(timers, timer_id) var/extended_time_display = DisplayTimeText(EXTENDED_CROSS_SECTOR_CANCEL_TIME) var/normal_time_display = DisplayTimeText(CROSS_SECTOR_CANCEL_TIME) var/message = "CROSS-SECTOR MESSAGE (INCOMING): [input["sender_ckey"]] (from [input["source"]]) is about to send \ the following message (will autoapprove in [soft_filter_passed ? "[extended_time_display]" : "[normal_time_display]"]): \ REJECT

\ [input["message"]]" if(soft_filter_passed) message += "

NOTE: This message passed the soft filter on the origin server! The time was automatically expanded to [extended_time_display]." message_admins(span_adminnotice(message)) /datum/world_topic/comms_console/Topic(href, list/href_list) . = ..() if (.) return if (href_list["reject_cross_comms_message"]) if (!usr.client?.holder) log_game("[key_name(usr)] tried to reject an incoming cross-comms message without being an admin.") message_admins("[key_name(usr)] tried to reject an incoming cross-comms message without being an admin.") return var/timer_id = href_list["reject_cross_comms_message"] if (!(timer_id in timers)) to_chat(usr, span_warning("It's too late!")) return deltimer(timer_id) LAZYREMOVE(timers, timer_id) log_admin("[key_name(usr)] has cancelled the incoming cross-comms message.") message_admins("[key_name(usr)] has cancelled the incoming cross-comms message.") return TRUE /datum/world_topic/comms_console/proc/receive_cross_comms_message(list/data) var/list/input = data["input"] var/timer_id = data["timer_id"] LAZYREMOVE(timers, timer_id) minor_announce(input["message"], "Incoming message from [input["message_sender"]]") message_admins("Receiving a message from [input["sender_ckey"]] at [input["source"]]") for(var/obj/machinery/computer/communications/communications_console in GLOB.shuttle_caller_list) communications_console.override_cooldown() /datum/world_topic/news_report keyword = "News_Report" /datum/world_topic/news_report/Run(list/input) minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") /datum/world_topic/adminmsg keyword = "adminmsg" /datum/world_topic/adminmsg/Run(list/input) return TgsPm(input[keyword], input["msg"], input["sender"]) /datum/world_topic/namecheck keyword = "namecheck" /datum/world_topic/namecheck/Run(list/input) log_admin("world/Topic Name Check: [input["sender"]] on [input["namecheck"]]") message_admins("Name checking [input["namecheck"]] from [input["sender"]] (World topic)") return keywords_lookup(input["namecheck"], 1) /datum/world_topic/adminwho keyword = "adminwho" /datum/world_topic/adminwho/Run(list/input) return tgsadminwho() /datum/world_topic/status keyword = "status" require_comms_key = FALSE /datum/world_topic/status/Run(list/input) . = list() .["version"] = GLOB.game_version .["respawn"] = config ? !!CONFIG_GET(flag/allow_respawn) : FALSE // show respawn as true regardless of "respawn as char" or "free respawn" .["enter"] = !LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS) .["ai"] = CONFIG_GET(flag/allow_ai) .["host"] = world.host ? world.host : null .["round_id"] = GLOB.round_id .["players"] = GLOB.clients.len .["revision"] = GLOB.revdata.commit .["revision_date"] = GLOB.revdata.date .["hub"] = GLOB.hub_visibility .["identifier"] = CONFIG_GET(string/serversqlname) var/public_address = CONFIG_GET(string/public_address) if(public_address) .["public_address"] = public_address var/list/adm = get_admin_counts() var/list/presentmins = adm["present"] var/list/afkmins = adm["afk"] .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho .["gamestate"] = SSticker.current_state .["map_name"] = SSmapping.current_map.map_name || "Loading..." if(key_valid) .["active_players"] = get_active_player_count() .["security_level"] = SSsecurity_level.get_current_level_as_text() .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 // Amount of world's ticks in seconds, useful for calculating round duration //Time dilation stats. .["time_dilation_current"] = SStime_track.time_dilation_current .["time_dilation_avg"] = SStime_track.time_dilation_avg .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast //pop cap stats .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 .["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 .["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE .["interviews"] = CONFIG_GET(flag/panic_bunker_interview) || FALSE if(SSshuttle?.emergency) .["shuttle_mode"] = SSshuttle.emergency.mode // Shuttle status, see /__DEFINES/stat.dm .["shuttle_timer"] = SSshuttle.emergency.timeLeft() // Shuttle timer, in seconds /datum/world_topic/create_news_channel keyword = "create_news_channel" /// Lazylist of timers for actually creating the channel to give admins some time var/list/timers /datum/world_topic/create_news_channel/Run(list/input) var/message_delay = text2num(input["delay"]) var/timer_id = addtimer(CALLBACK(src, PROC_REF(create_channel), input), message_delay) input["timer_id"] = timer_id LAZYADD(timers, timer_id) var/message = "Cross-sector channel creation (Incoming): [input["author_ckey"]] is about to create a cross-sector \ newscaster channel \"[input["message"]]\" (will autoapprove in [DisplayTimeText(message_delay)]): \ REJECT" message_admins(span_adminnotice(message)) /datum/world_topic/create_news_channel/Topic(href, list/href_list) . = ..() if (.) return var/timer_id = href_list["reject_channel_creation"] if (!timer_id) return if (!usr.client?.holder) log_game("tried to reject the creation of an incoming cross-sector newscaster channel without being an admin.", LOG_ADMIN) message_admins("[key_name(usr)] tried to reject the creation of an incoming cross-sector newscaster channel without being an admin.") return if (!(timer_id in timers)) to_chat(usr, span_warning("It's too late!")) return deltimer(timer_id) LAZYREMOVE(timers, timer_id) log_admin("[key_name(usr)] has cancelled the creation of an incoming cross-sector newscaster channel.") message_admins("[key_name(usr)] has cancelled the creation of an incoming cross-sector newscaster channel.") return TRUE /datum/world_topic/create_news_channel/proc/create_channel(list/input) LAZYREMOVE(timers, input["timer_id"]) message_admins("[input["author_ckey"]] has crated a cross-sector newscaster channel titled \"[input["message"]]\"") GLOB.news_network.create_feed_channel(input["message"], input["author"], input["desc"], locked = TRUE, receiving_cross_sector = TRUE) /datum/world_topic/create_news_article keyword = "create_news_article" /datum/world_topic/create_news_article/Run(list/input) var/msg = input["msg"] var/author = input["author"] var/author_key = input["author_ckey"] var/channel_name = input["message"] var/found_channel = FALSE for(var/datum/feed_channel/channel as anything in GLOB.news_network.network_channels) if(channel.channel_name == channel_name) found_channel = TRUE break // No channel with a matching name, abort if (!found_channel) return message_admins(span_adminnotice("Incoming cross-sector newscaster article by [author_key] in channel [channel_name].")) GLOB.news_network.submit_article(msg, author, channel_name)