From 54991b3f47f25b0c8fcdb46c177bacc2b13b7759 Mon Sep 17 00:00:00 2001 From: Neerti Date: Thu, 13 May 2021 02:22:25 -0400 Subject: [PATCH] Ports Nebula's Discord Webhook Integration --- code/__defines/color.dm | 6 + code/__defines/misc.dm | 15 +- code/__defines/subsystems.dm | 1 + code/__defines/webhooks.dm | 10 + code/_helpers/_lists.dm | 13 +- code/_helpers/text.dm | 23 + code/_helpers/type2type.dm | 14 + code/controllers/configuration.dm | 837 ++++++++++++++++++ code/controllers/subsystems/ticker.dm | 7 + code/controllers/subsystems/webhooks.dm | 94 ++ code/game/gamemodes/game_mode.dm | 9 + code/game/world.dm | 36 + code/modules/admin/admin.dm | 13 + code/modules/admin/admin_verb_lists.dm | 4 +- code/modules/admin/verbs/adminhelp.dm | 64 +- code/modules/admin/verbs/custom_event.dm | 7 + code/modules/paperwork/faxmachine.dm | 38 + code/modules/webhooks/_webhook.dm | 72 ++ .../modules/webhooks/webhook_ahelp2discord.dm | 13 + code/modules/webhooks/webhook_custom_event.dm | 11 + code/modules/webhooks/webhook_fax2discord.dm | 10 + code/modules/webhooks/webhook_roundend.dm | 26 + code/modules/webhooks/webhook_roundprep.dm | 17 + code/modules/webhooks/webhook_roundstart.dm | 16 + config/example/webhooks.json | 12 + vorestation.dme | 12 + 26 files changed, 1376 insertions(+), 4 deletions(-) create mode 100644 code/__defines/webhooks.dm create mode 100644 code/controllers/subsystems/webhooks.dm create mode 100644 code/modules/webhooks/_webhook.dm create mode 100644 code/modules/webhooks/webhook_ahelp2discord.dm create mode 100644 code/modules/webhooks/webhook_custom_event.dm create mode 100644 code/modules/webhooks/webhook_fax2discord.dm create mode 100644 code/modules/webhooks/webhook_roundend.dm create mode 100644 code/modules/webhooks/webhook_roundprep.dm create mode 100644 code/modules/webhooks/webhook_roundstart.dm create mode 100644 config/example/webhooks.json diff --git a/code/__defines/color.dm b/code/__defines/color.dm index 36745128134..3cb9715586b 100644 --- a/code/__defines/color.dm +++ b/code/__defines/color.dm @@ -165,3 +165,9 @@ #define COLOR_ASTEROID_ROCK "#735555" #define COLOR_GOLD "#ffcc33" + +// Discord requires colors to be in decimal instead of hexadecimal. +#define COLOR_WEBHOOK_DEFAULT 0x8bbbd5 // "#8bbbd5" +#define COLOR_WEBHOOK_GOOD 0x2ECC71 // "#2ECC71" +#define COLOR_WEBHOOK_POOR 0xE67E22 // "#E67E22" +#define COLOR_WEBHOOK_BAD 0xE74C3C // "#E74C3C" \ No newline at end of file diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index bb0387ab4ee..143eaceb37c 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -448,4 +448,17 @@ GLOBAL_LIST_INIT(all_volume_channels, list( #define LOADOUT_WHITELIST_OFF 0 #define LOADOUT_WHITELIST_LAX 1 -#define LOADOUT_WHITELIST_STRICT 2 \ No newline at end of file +#define LOADOUT_WHITELIST_STRICT 2 + + +#ifndef WINDOWS_HTTP_POST_DLL_LOCATION +#define WINDOWS_HTTP_POST_DLL_LOCATION "lib/byhttp.dll" +#endif + +#ifndef UNIX_HTTP_POST_DLL_LOCATION +#define UNIX_HTTP_POST_DLL_LOCATION "lib/libbyhttp.so" +#endif + +#ifndef HTTP_POST_DLL_LOCATION +#define HTTP_POST_DLL_LOCATION (world.system_type == MS_WINDOWS ? WINDOWS_HTTP_POST_DLL_LOCATION : UNIX_HTTP_POST_DLL_LOCATION) +#endif \ No newline at end of file diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index d24f0bfdd60..bb6592c5760 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -52,6 +52,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G // Subsystem init_order, from highest priority to lowest priority // Subsystems shutdown in the reverse of the order they initialize in // The numbers just define the ordering, they are meaningless otherwise. +#define INIT_ORDER_WEBHOOKS 50 #define INIT_ORDER_SQLITE 40 #define INIT_ORDER_CHEMISTRY 35 #define INIT_ORDER_SKYBOX 30 diff --git a/code/__defines/webhooks.dm b/code/__defines/webhooks.dm new file mode 100644 index 00000000000..13fcfa90e47 --- /dev/null +++ b/code/__defines/webhooks.dm @@ -0,0 +1,10 @@ +// Please don't forget to update the webhooks page on the GitHub Wiki page with your new webhook ID. +#define WEBHOOK_ROUNDEND "webhook_roundend" +#define WEBHOOK_ROUNDPREP "webhook_roundprep" +#define WEBHOOK_ROUNDSTART "webhook_roundstart" + +#define WEBHOOK_SUBMAP_LOADED "webhook_submap_loaded" +#define WEBHOOK_CUSTOM_EVENT "webhook_custom_event" +#define WEBHOOK_ELEVATOR_FALL "webhook_elevator_fall" +#define WEBHOOK_AHELP_SENT "webhook_ahelp_sent" +#define WEBHOOK_FAX_SENT "webhook_fax_sent" diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index abe05b5141f..a87307ac804 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -855,4 +855,15 @@ proc/dd_sortedTextList(list/incoming) result += pick(shifts) return result - \ No newline at end of file + +var/global/list/json_cache = list() +/proc/cached_json_decode(var/json_to_decode) + if(!json_to_decode || !length(json_to_decode)) + return list() + try + if(isnull(global.json_cache[json_to_decode])) + global.json_cache[json_to_decode] = json_decode(json_to_decode) + . = global.json_cache[json_to_decode] + catch(var/exception/e) + log_error("Exception during JSON decoding ([json_to_decode]): [e]") + return list() \ No newline at end of file diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index 2d59fa6b6a8..4a26745a6a9 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -507,3 +507,26 @@ proc/TextPreview(var/string,var/len=40) var/charcount = count - length_char(text) var/list/chars_to_add[max(charcount + 1, 0)] return text + jointext(chars_to_add, char) +<<<<<<< HEAD +======= + +//Readds quotes and apostrophes to HTML-encoded strings +/proc/readd_quotes(var/t) + var/list/repl_chars = list(""" = "\"","'" = "'") + for(var/char in repl_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+5) + index = findtext(t, char) + return t + +// Rips out paper HTML but tries to keep it semi-readable. +/proc/paper_html_to_plaintext(paper_text) + paper_text = replacetext(paper_text, "
", "-----") + paper_text = replacetext(paper_text, "
  • ", "- ") // This makes ordered lists turn into unordered but fixing that is too much effort. + paper_text = replacetext(paper_text, "
  • ", "\n") + paper_text = replacetext(paper_text, "

    ", "\n") + paper_text = replacetext(paper_text, "
    ", "\n") + paper_text = strip_html_properly(paper_text) // Get rid of everything else entirely. + return paper_text +>>>>>>> 4deffc7... Ports Nebula's Discord Webhook Integration (#8071) diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm index 52dada1cf96..b5659351be3 100644 --- a/code/_helpers/type2type.dm +++ b/code/_helpers/type2type.dm @@ -397,3 +397,17 @@ return /datum return text2path(copytext(string_type, 1, last_slash)) + +//checks if a file exists and contains text +//returns text as a string if these conditions are met +/proc/safe_file2text(filename, error_on_invalid_return = TRUE) + try + if(fexists(filename)) + . = file2text(filename) + if(!. && error_on_invalid_return) + error("File empty ([filename])") + else if(error_on_invalid_return) + error("File not found ([filename])") + catch(var/exception/E) + if(error_on_invalid_return) + error("Exception when loading file as string: [E]") \ No newline at end of file diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 1f82939221a..48432ff3f6b 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -287,6 +287,7 @@ var/list/gamemode_cache = list() var/static/enable_night_shifts = FALSE // How strictly the loadout enforces object species whitelists +<<<<<<< HEAD var/loadout_whitelist = LOADOUT_WHITELIST_LAX var/static/vgs_access_identifier = null // VOREStation Edit - VGS @@ -1126,3 +1127,839 @@ var/list/gamemode_cache = list() config.python_path = "/usr/bin/env python2" else //probably windows, if not this should work anyway config.python_path = "python" +======= + var/loadout_whitelist = LOADOUT_WHITELIST_LAX + + var/disable_webhook_embeds = FALSE + +/datum/configuration/New() + var/list/L = typesof(/datum/game_mode) - /datum/game_mode + for (var/T in L) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + if (M.config_tag) + gamemode_cache[M.config_tag] = M // So we don't instantiate them repeatedly. + if(!(M.config_tag in modes)) // ensure each mode is added only once + log_misc("Adding game mode [M.name] ([M.config_tag]) to configuration.") + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + player_requirements[M.config_tag] = M.required_players + player_requirements_secret[M.config_tag] = M.required_players_secret + if (M.votable) + src.votable_modes += M.config_tag + src.votable_modes += "secret" + +/datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist + var/list/Lines = file2list(filename) + + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + if(type == "config") + switch (name) + if ("resource_urls") + config.resource_urls = splittext(value, " ") + + if ("admin_legacy_system") + config.admin_legacy_system = 1 + + if ("ban_legacy_system") + config.ban_legacy_system = 1 + + if ("use_age_restriction_for_jobs") + config.use_age_restriction_for_jobs = 1 + + if ("use_age_restriction_for_antags") + config.use_age_restriction_for_antags = 1 + + if ("jobs_have_minimal_access") + config.jobs_have_minimal_access = 1 + + if ("use_recursive_explosions") + use_recursive_explosions = 1 + + if ("multi_z_explosion_scalar") + multi_z_explosion_scalar = text2num(value) + + if ("log_ooc") + config.log_ooc = 1 + + if ("log_access") + config.log_access = 1 + + if ("sql_enabled") + config.sql_enabled = 1 + + if ("log_say") + config.log_say = 1 + + if ("debug_paranoid") + config.debugparanoid = 1 + + if ("log_admin") + config.log_admin = 1 + + if ("log_debug") + config.log_debug = text2num(value) + + if ("log_game") + config.log_game = 1 + + if ("log_vote") + config.log_vote = 1 + + if ("log_whisper") + config.log_whisper = 1 + + if ("log_attack") + config.log_attack = 1 + + if ("log_emote") + config.log_emote = 1 + + if ("log_adminchat") + config.log_adminchat = 1 + + if ("log_adminwarn") + config.log_adminwarn = 1 + + if ("log_pda") + config.log_pda = 1 + + if ("log_world_output") + config.log_world_output = 1 + + if ("log_hrefs") + config.log_hrefs = 1 + + if ("log_runtime") + config.log_runtime = 1 + + if ("log_graffiti") + config.log_graffiti = 1 + + if ("generate_map") + config.generate_map = 1 + + if ("no_click_cooldown") + config.no_click_cooldown = 1 + + if("allow_admin_ooccolor") + config.allow_admin_ooccolor = 1 + + if ("allow_vote_restart") + config.allow_vote_restart = 1 + + if ("allow_vote_mode") + config.allow_vote_mode = 1 + + if ("allow_admin_jump") + config.allow_admin_jump = 1 + + if("allow_admin_rev") + config.allow_admin_rev = 1 + + if ("allow_admin_spawning") + config.allow_admin_spawning = 1 + + if ("allow_byond_links") + allow_byond_links = 1 + + if ("allow_discord_links") + allow_discord_links = 1 + + if ("allow_url_links") + allow_url_links = 1 + + if ("no_dead_vote") + config.vote_no_dead = 1 + + if ("default_no_vote") + config.vote_no_default = 1 + + if ("pregame_time") + config.pregame_time = text2num(value) + + if ("vote_delay") + config.vote_delay = text2num(value) + + if ("vote_period") + config.vote_period = text2num(value) + + if ("vote_autotransfer_initial") + config.vote_autotransfer_initial = text2num(value) + + if ("vote_autotransfer_interval") + config.vote_autotransfer_interval = text2num(value) + + if ("vote_autogamemode_timeleft") + config.vote_autogamemode_timeleft = text2num(value) + + if("ert_admin_only") + config.ert_admin_call_only = 1 + + if ("allow_ai") + config.allow_ai = 1 + + if ("allow_ai_shells") + config.allow_ai_shells = TRUE + + if("give_free_ai_shell") + config.give_free_ai_shell = TRUE + +// if ("authentication") +// config.enable_authentication = 1 + + if ("norespawn") + config.respawn = 0 + + if ("respawn_time") + var/raw_minutes = text2num(value) + config.respawn_time = raw_minutes MINUTES + + if ("respawn_message") + config.respawn_message = "[value]" + + if ("servername") + config.server_name = value + + if ("serversuffix") + config.server_suffix = 1 + + if ("nudge_script_path") + config.nudge_script_path = value + + if ("hostedby") + config.hostedby = value + + if ("serverurl") + config.serverurl = value + + if ("server") + config.server = value + + if ("banappeals") + config.banappeals = value + + if ("wikiurl") + config.wikiurl = value + + if ("wikisearchurl") + config.wikisearchurl = value + + if ("forumurl") + config.forumurl = value + + if ("rulesurl") + config.rulesurl = value + + if ("mapurl") + config.mapurl = value + + if ("githuburl") + config.githuburl = value + if ("guest_jobban") + config.guest_jobban = 1 + + if ("guest_ban") + config.guests_allowed = 0 + + if ("disable_ooc") + config.ooc_allowed = 0 + config.looc_allowed = 0 + + if ("disable_entry") + config.enter_allowed = 0 + + if ("disable_dead_ooc") + config.dooc_allowed = 0 + + if ("disable_dsay") + config.dsay_allowed = 0 + + if ("disable_respawn") + config.abandon_allowed = 0 + + if ("usewhitelist") + config.usewhitelist = 1 + + if ("feature_object_spell_system") + config.feature_object_spell_system = 1 + + if ("allow_metadata") + config.allow_Metadata = 1 + + if ("traitor_scaling") + config.traitor_scaling = 1 + + if ("aliens_allowed") + config.aliens_allowed = 1 + + if ("ninjas_allowed") + config.ninjas_allowed = 1 + + if ("objectives_disabled") + config.objectives_disabled = 1 + + if("protect_roles_from_antagonist") + config.protect_roles_from_antagonist = 1 + + if("persistence_disabled") + config.persistence_disabled = TRUE // Previously this forcibly set persistence enabled in the saves. + + if("persistence_ignore_mapload") + config.persistence_ignore_mapload = TRUE + + if ("probability") + var/prob_pos = findtext(value, " ") + var/prob_name = null + var/prob_value = null + + if (prob_pos) + prob_name = lowertext(copytext(value, 1, prob_pos)) + prob_value = copytext(value, prob_pos + 1) + if (prob_name in config.modes) + config.probabilities[prob_name] = text2num(prob_value) + else + log_misc("Unknown game mode probability configuration definition: [prob_name].") + else + log_misc("Incorrect probability configuration definition: [prob_name] [prob_value].") + + if ("required_players", "required_players_secret") + var/req_pos = findtext(value, " ") + var/req_name = null + var/req_value = null + var/is_secret_override = findtext(name, "required_players_secret") // Being extra sure we're not picking up an override for Secret by accident. + + if(req_pos) + req_name = lowertext(copytext(value, 1, req_pos)) + req_value = copytext(value, req_pos + 1) + if(req_name in config.modes) + if(is_secret_override) + config.player_requirements_secret[req_name] = text2num(req_value) + else + config.player_requirements[req_name] = text2num(req_value) + else + log_misc("Unknown game mode player requirement configuration definition: [req_name].") + else + log_misc("Incorrect player requirement configuration definition: [req_name] [req_value].") + + if("allow_random_events") + config.allow_random_events = 1 + + if("enable_game_master") + config.enable_game_master = 1 + + if("kick_inactive") + config.kick_inactive = text2num(value) + + if("show_mods") + config.show_mods = 1 + + if("show_devs") + config.show_devs = 1 + + if("show_event_managers") + config.show_event_managers = 1 + + if("mods_can_tempban") + config.mods_can_tempban = 1 + + if("mods_can_job_tempban") + config.mods_can_job_tempban = 1 + + if("mod_tempban_max") + config.mod_tempban_max = text2num(value) + + if("mod_job_tempban_max") + config.mod_job_tempban_max = text2num(value) + + if("load_jobs_from_txt") + load_jobs_from_txt = 1 + + if("alert_red_upto") + config.alert_desc_red_upto = value + + if("alert_red_downto") + config.alert_desc_red_downto = value + + if("alert_blue_downto") + config.alert_desc_blue_downto = value + + if("alert_blue_upto") + config.alert_desc_blue_upto = value + + if("alert_green") + config.alert_desc_green = value + + if("alert_delta") + config.alert_desc_delta = value + + if("forbid_singulo_possession") + forbid_singulo_possession = 1 + + if("popup_admin_pm") + config.popup_admin_pm = 1 + + if("allow_holidays") + Holiday = 1 + + if("use_irc_bot") + use_irc_bot = 1 + + if("use_node_bot") + use_node_bot = 1 + + if("irc_bot_port") + config.irc_bot_port = value + + if("irc_bot_export") + irc_bot_export = 1 + + if("ticklag") + var/ticklag = text2num(value) + if(ticklag > 0) + fps = 10 / ticklag + + if("tick_limit_mc_init") + tick_limit_mc_init = text2num(value) + + if("allow_antag_hud") + config.antag_hud_allowed = 1 + if("antag_hud_restricted") + config.antag_hud_restricted = 1 + + if("socket_talk") + socket_talk = text2num(value) + + if("tickcomp") + Tickcomp = 1 + + if("humans_need_surnames") + humans_need_surnames = 1 + + if("tor_ban") + ToRban = 1 + + if("automute_on") + automute_on = 1 + + if("usealienwhitelist") + usealienwhitelist = 1 + + if("alien_player_ratio") + limitalienplayers = 1 + alien_to_human_ratio = text2num(value) + + if("assistant_maint") + config.assistant_maint = 1 + + if("gateway_delay") + config.gateway_delay = text2num(value) + + if("continuous_rounds") + config.continous_rounds = 1 + + if("ghost_interaction") + config.ghost_interaction = 1 + + if("disable_player_mice") + config.disable_player_mice = 1 + + if("uneducated_mice") + config.uneducated_mice = 1 + + if("comms_password") + config.comms_password = value + + if("irc_bot_host") + config.irc_bot_host = value + + if("main_irc") + config.main_irc = value + + if("admin_irc") + config.admin_irc = value + + if("python_path") + if(value) + config.python_path = value + + if("use_lib_nudge") + config.use_lib_nudge = 1 + + if("allow_cult_ghostwriter") + config.cult_ghostwriter = 1 + + if("req_cult_ghostwriter") + config.cult_ghostwriter_req_cultists = text2num(value) + + if("character_slots") + config.character_slots = text2num(value) + + if("loadout_slots") + config.loadout_slots = text2num(value) + + if("allow_drone_spawn") + config.allow_drone_spawn = text2num(value) + + if("drone_build_time") + config.drone_build_time = text2num(value) + + if("max_maint_drones") + config.max_maint_drones = text2num(value) + + if("use_overmap") + config.use_overmap = 1 + + if("engine_map") + config.engine_map = splittext(value, ",") +/* + if("station_levels") + using_map.station_levels = text2numlist(value, ";") + + if("admin_levels") + using_map.admin_levels = text2numlist(value, ";") + + if("contact_levels") + using_map.contact_levels = text2numlist(value, ";") + + if("player_levels") + using_map.player_levels = text2numlist(value, ";") +*/ + if("expected_round_length") + config.expected_round_length = MinutesToTicks(text2num(value)) + + if("disable_welder_vision") + config.welder_vision = 0 + + if("allow_extra_antags") + config.allow_extra_antags = 1 + + if("event_custom_start_mundane") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MUNDANE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_moderate") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MODERATE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_major") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MAJOR] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_delay_lower") + var/values = text2numlist(value, ";") + config.event_delay_lower[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_lower[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_lower[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("event_delay_upper") + var/values = text2numlist(value, ";") + config.event_delay_upper[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_upper[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("starlight") + value = text2num(value) + config.starlight = value >= 0 ? value : 0 + + if("ert_species") + config.ert_species = splittext(value, ";") + if(!config.ert_species.len) + config.ert_species += SPECIES_HUMAN + + if("law_zero") + law_zero = value + + if("aggressive_changelog") + config.aggressive_changelog = 1 + + if("default_language_prefixes") + var/list/values = splittext(value, " ") + if(values.len > 0) + language_prefixes = values + + if("radiation_lower_limit") + radiation_lower_limit = text2num(value) + + if("radiation_resistance_calc_divide") + radiation_resistance_calc_mode = RAD_RESIST_CALC_DIV + + if("radiation_resistance_calc_subtract") + radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB + + if("radiation_resistance_multiplier") + radiation_resistance_multiplier = text2num(value) + + if("radiation_material_resistance_divisor") + radiation_material_resistance_divisor = text2num(value) + + if("radiation_decay_rate") + radiation_decay_rate = text2num(value) + + if ("panic_bunker") + config.panic_bunker = 1 + + if ("paranoia_logging") + config.paranoia_logging = 1 + + if("ip_reputation") + config.ip_reputation = 1 + + if("ipr_email") + config.ipr_email = value + + if("ipr_block_bad_ips") + config.ipr_block_bad_ips = 1 + + if("ipr_bad_score") + config.ipr_bad_score = text2num(value) + + if("ipr_allow_existing") + config.ipr_allow_existing = 1 + + if("ipr_minimum_age") + config.ipr_minimum_age = text2num(value) + + if("random_submap_orientation") + config.random_submap_orientation = 1 + + if("autostart_solars") + config.autostart_solars = TRUE + + if("sqlite_enabled") + config.sqlite_enabled = TRUE + + if("sqlite_feedback") + config.sqlite_feedback = TRUE + + if("sqlite_feedback_topics") + config.sqlite_feedback_topics = splittext(value, ";") + if(!config.sqlite_feedback_topics.len) + config.sqlite_feedback_topics += "General" + + if("sqlite_feedback_privacy") + config.sqlite_feedback_privacy = TRUE + + if("sqlite_feedback_cooldown") + config.sqlite_feedback_cooldown = text2num(value) + + if("defib_timer") + config.defib_timer = text2num(value) + + if("defib_braindamage_timer") + config.defib_braindamage_timer = text2num(value) + + if("disable_cid_warn_popup") + config.disable_cid_warn_popup = TRUE + + if("enable_night_shifts") + config.enable_night_shifts = TRUE + + else + log_misc("Unknown setting in configuration: '[name]'") + + else if(type == "game_options") + if(!value) + log_misc("Unknown value for setting [name] in [filename].") + value = text2num(value) + + switch(name) + if("health_threshold_crit") + config.health_threshold_crit = value + if("health_threshold_softcrit") + config.health_threshold_softcrit = value + if("health_threshold_dead") + config.health_threshold_dead = value + if("show_human_death_message") + config.show_human_death_message = 1 + if("revival_pod_plants") + config.revival_pod_plants = value + if("revival_cloning") + config.revival_cloning = value + if("revival_brain_life") + config.revival_brain_life = value + if("organ_health_multiplier") + config.organ_health_multiplier = value / 100 + if("organ_regeneration_multiplier") + config.organ_regeneration_multiplier = value / 100 + if("organ_damage_spillover_multiplier") + config.organ_damage_spillover_multiplier = value / 100 + if("organs_can_decay") + config.organs_decay = 1 + if("default_brain_health") + config.default_brain_health = text2num(value) + if(!config.default_brain_health || config.default_brain_health < 1) + config.default_brain_health = initial(config.default_brain_health) + if("bones_can_break") + config.bones_can_break = value + if("limbs_can_break") + config.limbs_can_break = value + if("allow_headgibs") + config.allow_headgibs = TRUE + + if("run_speed") + config.run_speed = value + if("walk_speed") + config.walk_speed = value + + if("human_delay") + config.human_delay = value + if("robot_delay") + config.robot_delay = value + if("monkey_delay") + config.monkey_delay = value + if("alien_delay") + config.alien_delay = value + if("slime_delay") + config.slime_delay = value + if("animal_delay") + config.animal_delay = value + + if("footstep_volume") + config.footstep_volume = text2num(value) + + if("use_loyalty_implants") + config.use_loyalty_implants = 1 + + if("loadout_whitelist") + config.loadout_whitelist = text2num(value) + + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + sqladdress = value + if ("port") + sqlport = value + if ("database") + sqldb = value + if ("login") + sqllogin = value + if ("password") + sqlpass = value + if ("feedback_database") + sqlfdbkdb = value + if ("feedback_login") + sqlfdbklogin = value + if ("feedback_password") + sqlfdbkpass = value + if ("enable_stat_tracking") + sqllogging = 1 + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadforumsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + forumsqladdress = value + if ("port") + forumsqlport = value + if ("database") + forumsqldb = value + if ("login") + forumsqllogin = value + if ("password") + forumsqlpass = value + if ("activatedgroup") + forum_activated_group = value + if ("authenticatedgroup") + forum_authenticated_group = value + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + for (var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if (M.config_tag && M.config_tag == mode_name) + return M + return gamemode_cache["extended"] + +/datum/configuration/proc/get_runnable_modes() + var/list/runnable_modes = list() + for(var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if(M && M.can_start() && !isnull(config.probabilities[M.config_tag]) && config.probabilities[M.config_tag] > 0) + runnable_modes |= M + return runnable_modes + +/datum/configuration/proc/post_load() + //apply a default value to config.python_path, if needed + if (!config.python_path) + if(world.system_type == UNIX) + config.python_path = "/usr/bin/env python2" + else //probably windows, if not this should work anyway + config.python_path = "python" +>>>>>>> 4deffc7... Ports Nebula's Discord Webhook Integration (#8071) diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index 47f35a27a82..7d7b123298d 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -49,6 +49,13 @@ var/global/datum/controller/subsystem/ticker/ticker /datum/controller/subsystem/ticker/Initialize() pregame_timeleft = config.pregame_time send2mainirc("Server lobby is loaded and open at byond://[config.serverurl ? config.serverurl : (config.server ? config.server : "[world.address]:[world.port]")]") + SSwebhooks.send( + WEBHOOK_ROUNDPREP, + list( + "map" = station_name(), + "url" = get_world_url() + ) + ) GLOB.autospeaker = new (null, null, null, 1) //Set up Global Announcer return ..() diff --git a/code/controllers/subsystems/webhooks.dm b/code/controllers/subsystems/webhooks.dm new file mode 100644 index 00000000000..25252d30504 --- /dev/null +++ b/code/controllers/subsystems/webhooks.dm @@ -0,0 +1,94 @@ +SUBSYSTEM_DEF(webhooks) + name = "Webhooks" + init_order = INIT_ORDER_WEBHOOKS + flags = SS_NO_FIRE + var/list/webhook_decls = list() + +/datum/controller/subsystem/webhooks/Initialize() + load_webhooks() + . = ..() + +/datum/controller/subsystem/webhooks/proc/load_webhooks() + + if(!fexists(HTTP_POST_DLL_LOCATION)) + to_world_log("Unable to locate HTTP POST lib at [HTTP_POST_DLL_LOCATION], webhooks will not function on this run.") + return + + var/list/all_webhooks_by_id = list() + var/list/all_webhooks = decls_repository.get_decls_of_subtype(/decl/webhook) + for(var/wid in all_webhooks) + var/decl/webhook/webhook = all_webhooks[wid] + if(webhook.id) + all_webhooks_by_id[webhook.id] = webhook + + webhook_decls.Cut() + var/webhook_config = safe_file2text("config/webhooks.json") + if(webhook_config) + for(var/webhook_data in cached_json_decode(webhook_config)) + var/wid = webhook_data["id"] + var/wurl = webhook_data["url"] + var/list/wmention = webhook_data["mentions"] + if(wmention && !islist(wmention)) + wmention = list(wmention) + to_world_log("Setting up webhook [wid].") + if(wid && wurl && all_webhooks_by_id[wid]) + var/decl/webhook/webhook = all_webhooks_by_id[wid] + webhook.urls = islist(wurl) ? wurl : list(wurl) + for(var/url in webhook.urls) + if(!webhook.urls[url]) + webhook.urls[url] = list() + else if(!islist(webhook.urls[url])) + webhook.urls[url] = list(webhook.urls[url]) + if(wmention) + webhook.mentions = wmention?.Copy() + webhook_decls[wid] = webhook + to_world_log("Webhook [wid] ready.") + else + to_world_log("Failed to set up webhook [wid].") + +/datum/controller/subsystem/webhooks/proc/send(var/wid, var/wdata) + var/decl/webhook/webhook = webhook_decls[wid] + if(webhook) + if(webhook.send(wdata)) + to_world_log("Sent webhook [webhook.id].") + log_debug("Webhook sent: [webhook.id].") + else + to_world_log("Failed to send webhook [webhook.id].") + log_debug("Webhook failed to send: [webhook.id].") + +/client/proc/reload_webhooks() + set name = "Reload Webhooks" + set category = "Debug" + + if(!holder) + return + + if(!SSwebhooks.subsystem_initialized) + to_chat(usr, SPAN_WARNING("Let the webhook subsystem initialize before trying to reload it.")) + return + + to_world_log("[usr.key] has reloaded webhooks.") + log_and_message_admins("has reloaded webhooks.") + SSwebhooks.load_webhooks() + +/client/proc/ping_webhook() + set name = "Ping Webhook" + set category = "Debug" + + if(!holder) + return + + if(!length(SSwebhooks.webhook_decls)) + to_chat(usr, SPAN_WARNING("Webhook list is empty; either webhooks are disabled, webhooks aren't configured, or the subsystem hasn't initialized.")) + return + + var/choice = input(usr, "Select a webhook to ping.", "Ping Webhook") as null|anything in SSwebhooks.webhook_decls + if(choice && SSwebhooks.webhook_decls[choice]) + var/decl/webhook/webhook = SSwebhooks.webhook_decls[choice] + log_and_message_admins("has pinged webhook [choice].", usr) + to_world_log("[usr.key] has pinged webhook [choice].") + webhook.send() + +/hook/roundstart/proc/run_webhook() + SSwebhooks.send(WEBHOOK_ROUNDSTART, list("url" = get_world_url())) + return 1 diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index a1df0327933..98a41a2be42 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -368,6 +368,15 @@ var/global/list/additional_antag_types = list() feedback_set("escaped_on_pod_5",escaped_on_pod_5) send2mainirc("A round of [src.name] has ended - [surviving_total] survivors, [ghosts] ghosts.") + SSwebhooks.send( + WEBHOOK_ROUNDEND, + list( + "survivors" = surviving_total, + "escaped" = escaped_total, + "ghosts" = ghosts, + "clients" = clients + ) + ) return 0 diff --git a/code/game/world.dm b/code/game/world.dm index a51b7185de0..acd9845e5c7 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -677,3 +677,39 @@ proc/establish_old_db_connection() SStimer?.reset_buckets() #undef FAILED_DB_CONNECTION_CUTOFF + +/proc/get_world_url() + . = "byond://" + if(config.serverurl) + . += config.serverurl + else if(config.server) + . += config.server + else + . += "[world.address]:[world.port]" + +var/global/game_id = null + +/hook/startup/proc/generate_gameid() + if(game_id != null) + return + game_id = "" + + var/list/c = list( + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" + ) + var/l = c.len + + var/t = world.timeofday + for(var/_ = 1 to 4) + game_id = "[c[(t % l) + 1]][game_id]" + t = round(t / l) + game_id = "-[game_id]" + t = round(world.realtime / (10 * 60 * 60 * 24)) + for(var/_ = 1 to 3) + game_id = "[c[(t % l) + 1]][game_id]" + t = round(t / l) + return 1 \ No newline at end of file diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index b70af8354d5..9f50f3be1d7 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1630,6 +1630,19 @@ datum/admins/var/obj/item/weapon/paper/admin/faxreply // var to hold fax replies for(var/client/C in GLOB.admins) if((R_ADMIN | R_MOD | R_EVENT) & C.holder.rights) to_chat(C, "FAX LOG:[key_name_admin(src.owner)] has sent a fax message to [destination.department] (VIEW)") + + var/plaintext_title = P.sender ? "replied to [key_name(P.sender)]'s fax" : "sent a fax message to [destination.department]" + var/fax_text = paper_html_to_plaintext(P.info) + log_game(plaintext_title) + log_game(fax_text) + + SSwebhooks.send( + WEBHOOK_FAX_SENT, + list( + "name" = "[key_name(owner)] [plaintext_title].", + "body" = fax_text + ) + ) else to_chat(src.owner, "Message reply failed.") diff --git a/code/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm index 7f4db66b3a0..bee29ed14f8 100644 --- a/code/modules/admin/admin_verb_lists.dm +++ b/code/modules/admin/admin_verb_lists.dm @@ -235,7 +235,9 @@ var/list/admin_verbs_debug = list( /client/proc/admin_give_modifier, /client/proc/simple_DPS, /datum/admins/proc/view_feedback, - /client/proc/debug_global_variables + /client/proc/debug_global_variables, + /client/proc/ping_webhook, + /client/proc/reload_webhooks ) var/list/admin_verbs_paranoid_debug = list( diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index e294514b914..98436a2cec4 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -183,7 +183,22 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/admin_number_present = send2irc_adminless_only(initiator_ckey, name) log_admin("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) +<<<<<<< HEAD to_chat(C, "No active admins are online, your adminhelp was sent to the admin discord.") //VOREStation Edit +======= + to_chat(C, "No active admins are online, your adminhelp was sent to the admin irc.") + + // Also send it to discord since that's the hip cool thing now. + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) ticket opened.", + "body" = "[key_name(initiator)] has opened a ticket. \n[msg]", + "color" = COLOR_WEBHOOK_POOR + ) + ) + +>>>>>>> 4deffc7... Ports Nebula's Discord Webhook Integration (#8071) GLOB.ahelp_tickets.active_tickets += src /datum/admin_help/Destroy() @@ -273,6 +288,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) feedback_inc("ahelp_reopen") TicketPanel() //can only be done from here, so refresh it + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) reopened.", + "body" = "Reopened by [key_name(usr)]." + ) + ) + //private /datum/admin_help/proc/RemoveActive() if(state != AHELP_ACTIVE) @@ -298,6 +321,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name_admin(usr)]." message_admins(msg) log_admin(msg) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) closed.", + "body" = "Closed by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Mark open ticket as resolved/legitimate, returns ahelp verb /datum/admin_help/proc/Resolve(silent = FALSE) @@ -315,6 +346,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]" message_admins(msg) log_admin(msg) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) resolved.", + "body" = "Marked as Resolved by [key_name(usr)].", + "color" = COLOR_WEBHOOK_GOOD + ) + ) //Close and return ahelp verb, use if ticket is incoherent /datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) @@ -335,6 +374,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) log_admin(msg) AddInteraction("Rejected by [key_name_admin(usr)].") Close(silent = TRUE) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) rejected.", + "body" = "Rejected by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Resolve ticket with IC Issue message /datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) @@ -354,6 +401,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) log_admin(msg) AddInteraction("Marked as IC issue by [key_name_admin(usr)]") Resolve(silent = TRUE) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) marked as IC issue.", + "body" = "Marked as IC Issue by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Resolve ticket with IC Issue message /datum/admin_help/proc/HandleIssue() @@ -365,11 +420,18 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(initiator) to_chat(initiator, msg) - feedback_inc("ahelp_icissue") + feedback_inc("ahelp_handling") msg = "Ticket [TicketHref("#[id]")] being handled by [key_name(usr,FALSE,FALSE)]" message_admins(msg) log_admin(msg) AddInteraction("[key_name_admin(usr)] is now handling this ticket.") + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) being handled.", + "body" = "[key_name(usr)] is now handling the ticket." + ) + ) //Show the ticket panel /datum/admin_help/proc/TicketPanel() diff --git a/code/modules/admin/verbs/custom_event.dm b/code/modules/admin/verbs/custom_event.dm index 5d9cfd32691..6b92e9932e2 100644 --- a/code/modules/admin/verbs/custom_event.dm +++ b/code/modules/admin/verbs/custom_event.dm @@ -24,6 +24,13 @@ to_world("[custom_event_msg]") to_world("
    ") + SSwebhooks.send( + WEBHOOK_CUSTOM_EVENT, + list( + "text" = custom_event_msg, + ) + ) + // normal verb for players to view info /client/verb/cmd_view_custom_event() set category = "OOC" diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 9f4a183b31e..226aedcd5a9 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -222,6 +222,21 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins sleep(50) visible_message("[src] beeps, \"Message transmitted successfully.\"") +// Turns objects into just text. +/obj/machinery/photocopier/faxmachine/proc/make_summary(obj/item/sent) + if(istype(sent, /obj/item/weapon/paper)) + var/obj/item/weapon/paper/P = sent + return P.info + if(istype(sent, /obj/item/weapon/paper_bundle)) + . = "" + var/obj/item/weapon/paper_bundle/B = sent + for(var/i in 1 to B.pages.len) + var/obj/item/weapon/paper/P = B.pages[i] + if(istype(P)) // Photos can show up here too. + if(.) // Space out different pages. + . += "
    " + . += "PAGE [i] - [P.name]
    " + . += P.info /obj/machinery/photocopier/faxmachine/proc/message_admins(var/mob/sender, var/faxname, var/obj/item/sent, var/reply_type, font_colour="#006100") var/msg = "[faxname]: [get_options_bar(sender, 2,1,1)]" @@ -232,8 +247,31 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins if(check_rights((R_ADMIN|R_MOD|R_EVENT),0,C)) to_chat(C,msg) C << 'sound/machines/printer.ogg' +<<<<<<< HEAD // VoreStation Edit Start var/faxid = export_fax(sent) message_chat_admins(sender, faxname, sent, faxid, font_colour) // VoreStation Edit End +======= + + // Webhooks don't parse the HTML on the paper, so we gotta strip them out so it's still readable. + var/summary = make_summary(sent) + summary = paper_html_to_plaintext(summary) + + log_game("Fax to [lowertext(faxname)] was sent by [key_name(sender)].") + log_game(summary) + + var/webhook_length_limit = 1900 // The actual limit is a little higher. + if(length(summary) > webhook_length_limit) + summary = copytext(summary, 1, webhook_length_limit + 1) + summary += "\n\[Truncated\]" + + SSwebhooks.send( + WEBHOOK_FAX_SENT, + list( + "name" = "[faxname] '[sent.name]' sent from [key_name(sender)]", + "body" = summary + ) + ) +>>>>>>> 4deffc7... Ports Nebula's Discord Webhook Integration (#8071) diff --git a/code/modules/webhooks/_webhook.dm b/code/modules/webhooks/_webhook.dm new file mode 100644 index 00000000000..e7cfb646cd7 --- /dev/null +++ b/code/modules/webhooks/_webhook.dm @@ -0,0 +1,72 @@ +/decl/webhook + var/id + var/list/urls + var/list/mentions + +/decl/webhook/proc/get_message(var/list/data) + . = list() + +/decl/webhook/proc/http_post(var/target_url, var/payload) + if (!target_url) + return -1 + + var/result = call(HTTP_POST_DLL_LOCATION, "send_post_request")(target_url, payload, json_encode(list("Content-Type" = "application/json"))) + + result = cached_json_decode(result) + if (result["error_code"]) + log_debug("byhttp error: [result["error"]] ([result["error_code"]])") + return result["error_code"] + + return list( + "status_code" = result["status_code"], + "body" = result["body"] + ) + +/decl/webhook/proc/send(var/list/data) + var/list/message = get_message(data) + if(!length(message)) + return FALSE + + if(config.disable_webhook_embeds) + var/list/embed_content + for(var/list/embed in message["embeds"]) + if(embed["title"]) + LAZYADD(embed_content, "**[embed["title"]]**") + if(embed["description"]) + LAZYADD(embed_content, embed["description"]) + if(length(embed_content)) + if(message["content"]) + message["content"] = "[message["content"]]\n[jointext(embed_content, "\n")]" + else + message["content"] = jointext(embed_content, "\n") + message -= "embeds" + + . = TRUE + for(var/target_url in urls) + + var/url_message = message.Copy() + var/list/url_mentions = get_mentions(target_url) + if(islist(url_mentions) && length(url_mentions)) + if(url_message["content"]) + url_message["content"] = "[jointext(url_mentions, ", ")]: [url_message["content"]]" + else + url_message["content"] = "[jointext(url_mentions, ", ")]" + + var/list/httpresponse = http_post(target_url, json_encode(url_message)) + if(!islist(httpresponse)) + . = FALSE + continue + switch(httpresponse["status_code"]) + if (200 to 299) + continue + if (400 to 599) + log_debug("Webhooks: HTTP error code while sending to '[target_url]': [httpresponse["status_code"]]. Data: [httpresponse["body"]].") + else + log_debug("Webhooks: unknown HTTP code while sending to '[target_url]': [httpresponse["status_code"]]. Data: [httpresponse["body"]].") + . = FALSE + +/decl/webhook/proc/get_mentions(var/mentioning_url) + . = mentions?.Copy() + var/url_mentions = LAZYACCESS(urls, mentioning_url) + if(length(url_mentions)) + LAZYDISTINCTADD(., url_mentions) diff --git a/code/modules/webhooks/webhook_ahelp2discord.dm b/code/modules/webhooks/webhook_ahelp2discord.dm new file mode 100644 index 00000000000..3ae72d3bfe5 --- /dev/null +++ b/code/modules/webhooks/webhook_ahelp2discord.dm @@ -0,0 +1,13 @@ +/decl/webhook/ahelp_sent + id = WEBHOOK_AHELP_SENT + +/decl/webhook/ahelp_sent/get_message(var/list/data) + .= ..() + .["embeds"] = list(list( + "title" = "[data["name"]]", + "description" = data["body"], + "color" = data["color"] || COLOR_WEBHOOK_DEFAULT + )) + +/decl/webhook/ahelp_sent/get_mentions() + . = !length(global.admins) && ..() \ No newline at end of file diff --git a/code/modules/webhooks/webhook_custom_event.dm b/code/modules/webhooks/webhook_custom_event.dm new file mode 100644 index 00000000000..5f636db2569 --- /dev/null +++ b/code/modules/webhooks/webhook_custom_event.dm @@ -0,0 +1,11 @@ +/decl/webhook/custom_event + id = WEBHOOK_CUSTOM_EVENT + +// Data expects a "text" field containing the new custom event text. +/decl/webhook/custom_event/get_message(var/list/data) + . = ..() + .["embeds"] = list(list( + "title" = "A custom event is beginning.", + "description" = (data && data["text"]) || "undefined", + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_fax2discord.dm b/code/modules/webhooks/webhook_fax2discord.dm new file mode 100644 index 00000000000..336a01d1500 --- /dev/null +++ b/code/modules/webhooks/webhook_fax2discord.dm @@ -0,0 +1,10 @@ +/decl/webhook/fax_sent + id = WEBHOOK_FAX_SENT + +/decl/webhook/fax_sent/get_message(var/list/data) + .= ..() + .["embeds"] = list(list( + "title" = "[data["name"]]", + "description" = data["body"], + "color" = COLOR_WEBHOOK_DEFAULT + )) \ No newline at end of file diff --git a/code/modules/webhooks/webhook_roundend.dm b/code/modules/webhooks/webhook_roundend.dm new file mode 100644 index 00000000000..9c806e5a1dc --- /dev/null +++ b/code/modules/webhooks/webhook_roundend.dm @@ -0,0 +1,26 @@ +/decl/webhook/roundend + id = WEBHOOK_ROUNDEND + +// Data expects three numerical fields: "survivors", "escaped", "ghosts", "clients" +/decl/webhook/roundend/get_message(var/list/data) + . = ..() + var/desc = "A round of **[SSticker.mode ? SSticker.mode.name : "Unknown"]** ([game_id]) has ended.\n\n" + if(data) + var/s_escaped = "Escaped" + if(!emergency_shuttle.evac) + s_escaped = "Transferred" + if(data["survivors"] > 0) + desc += "Survivors: **[data["survivors"]]**\n" + desc += "[s_escaped]: **[data["escaped"]]**\n" + else + desc += "There were **no survivors**.\n\n" + desc += "Ghosts: **[data["ghosts"]]**\n" + desc += "Players: **[data["clients"]]**\n" + desc += "Round duration: **[roundduration2text()]**" + + .["embeds"] = list(list( + // "title" = global.end_credits_title, + "title" = "Round Has Ended", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_roundprep.dm b/code/modules/webhooks/webhook_roundprep.dm new file mode 100644 index 00000000000..b10580452f2 --- /dev/null +++ b/code/modules/webhooks/webhook_roundprep.dm @@ -0,0 +1,17 @@ +/decl/webhook/roundprep + id = WEBHOOK_ROUNDPREP + +// Data expects "url" and field pointing to the current hosted server and port to connect on. +/decl/webhook/roundprep/get_message(var/list/data) + . = ..() + var/desc = "The server has been started!\n" + if(data && data["map"]) + desc += "Map: **[data["map"]]**\n" + if(data && data["url"]) + desc += "Address: <[data["url"]]>" + + .["embeds"] = list(list( + "title" = "New round is being set up.", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_roundstart.dm b/code/modules/webhooks/webhook_roundstart.dm new file mode 100644 index 00000000000..f4afaa2bc4a --- /dev/null +++ b/code/modules/webhooks/webhook_roundstart.dm @@ -0,0 +1,16 @@ +/decl/webhook/roundstart + id = WEBHOOK_ROUNDSTART + +// Data expects a "url" field pointing to the current hosted server and port to connect on. +/decl/webhook/roundstart/get_message(var/list/data) + . = ..() + var/desc = "Gamemode: **[SSticker.mode.name]**\n" + desc += "Players: **[global.player_list.len]**" + if(data && data["url"]) + desc += "\nAddress: <[data["url"]]>" + + .["embeds"] = list(list( + "title" = "Round has started.", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/config/example/webhooks.json b/config/example/webhooks.json new file mode 100644 index 00000000000..fc8ea883fbc --- /dev/null +++ b/config/example/webhooks.json @@ -0,0 +1,12 @@ +[ + { + "id" : "webhook_roundend", + "url" : { + "someurl0" : [], + "someurl1" : [], + "someurl2" : "somemention0", + "someurl3" : [ "somemention1", "somemention2" ] + }, + "mentions" : [ "somemention3", "somemention4" ] + } +] diff --git a/vorestation.dme b/vorestation.dme index aac8ba93e52..b8ff6a2e893 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -97,6 +97,7 @@ #include "code\__defines\unit_tests.dm" #include "code\__defines\vote.dm" #include "code\__defines\vv.dm" +#include "code\__defines\webhooks.dm" #include "code\__defines\wires.dm" #include "code\__defines\xenoarcheaology.dm" #include "code\__defines\ZAS.dm" @@ -290,6 +291,7 @@ #include "code\controllers\subsystems\timer.dm" #include "code\controllers\subsystems\transcore_vr.dm" #include "code\controllers\subsystems\vote.dm" +#include "code\controllers\subsystems\webhooks.dm" #include "code\controllers\subsystems\xenoarch.dm" #include "code\controllers\subsystems\processing\bellies_vr.dm" #include "code\controllers\subsystems\processing\fastprocess.dm" @@ -3819,6 +3821,7 @@ #include "code\modules\virus2\helpers.dm" #include "code\modules\virus2\isolator.dm" #include "code\modules\virus2\items_devices.dm" +<<<<<<< HEAD:vorestation.dme #include "code\modules\vore\hook-defs_vr.dm" #include "code\modules\vore\trycatch_vr.dm" #include "code\modules\vore\appearance\preferences_vr.dm" @@ -3849,6 +3852,15 @@ #include "code\modules\vore\resizing\sizegun_vr.dm" #include "code\modules\vore\smoleworld\smoleworld_vr.dm" #include "code\modules\vore\weight\fitness_machines_vr.dm" +======= +#include "code\modules\webhooks\_webhook.dm" +#include "code\modules\webhooks\webhook_ahelp2discord.dm" +#include "code\modules\webhooks\webhook_custom_event.dm" +#include "code\modules\webhooks\webhook_fax2discord.dm" +#include "code\modules\webhooks\webhook_roundend.dm" +#include "code\modules\webhooks\webhook_roundprep.dm" +#include "code\modules\webhooks\webhook_roundstart.dm" +>>>>>>> 4deffc7... Ports Nebula's Discord Webhook Integration (#8071):polaris.dme #include "code\modules\xenoarcheaology\anomaly_container.dm" #include "code\modules\xenoarcheaology\boulder.dm" #include "code\modules\xenoarcheaology\effect.dm"