SShttp + SSdiscord | ASYNCHRONOUS STUFF IN BYOND! (#14762)

* SShttp + SSdiscord | ASYNCHRONOUS STUFF IN BYOND!

* Cleanup

* HTTP Callback example

* Fixes rust instability

* More refactors

* This works

* The sanitizer (Now worth £3000)

* New configs + other stuff

* Lets give this a shot

* Farie changes

* Mentor support

* Farie fixes
This commit is contained in:
AffectedArc07
2020-11-07 11:57:47 -05:00
committed by GitHub
parent dbe4e00f43
commit 56752b0e78
29 changed files with 652 additions and 213 deletions
+33 -25
View File
@@ -164,13 +164,6 @@
var/comms_password = ""
var/use_irc_bot = 0
var/list/irc_bot_host = list()
var/main_irc = ""
var/admin_irc = ""
var/admin_notify_irc = ""
var/cidrandomizer_irc = ""
var/default_laws = 0 //Controls what laws the AI spawns with.
var/list/station_levels = list(1) // Defines which Z-levels the station exists on.
@@ -253,6 +246,25 @@
/// BYOND account age limit for notifcations of new accounts (Any accounts older than this value will not send notifications on first join)
var/byond_account_age_threshold = 7
/// Are discord webhooks enabled?
var/discord_webhooks_enabled = FALSE
/// Role ID to be pinged for administrative events
var/discord_admin_role_id = null // Intentional null usage
/// Webhook URL for the main public webhook
var/discord_main_webhook_url
/// Webhook URL for the admin webhook
var/discord_admin_webhook_url
/// Webhook URL for the mentor webhook
var/discord_mentor_webhook_url
/// Do we want to forward all adminhelps to the discord or just ahelps when admins are offline.
/// (This does not mean all ahelps are pinged, only ahelps sent when staff are offline get the ping, regardless of this setting)
var/discord_forward_all_ahelps = FALSE
/datum/configuration/New()
for(var/T in subtypesof(/datum/game_mode))
var/datum/game_mode/M = T
@@ -562,9 +574,6 @@
if("allow_holidays")
config.allow_holidays = 1
if("use_irc_bot")
use_irc_bot = 1
if("ticklag")
Ticklag = text2num(value)
@@ -605,21 +614,6 @@
if("comms_password")
config.comms_password = value
if("irc_bot_host")
config.irc_bot_host = splittext(value, ";")
if("main_irc")
config.main_irc = value
if("admin_irc")
config.admin_irc = value
if("admin_notify_irc")
config.admin_notify_irc = value
if("cidrandomizer_irc")
config.cidrandomizer_irc = value
if("python_path")
if(value)
GLOB.python_path = value
@@ -746,6 +740,20 @@
config.enable_gamemode_player_limit = 1
if("byond_account_age_threshold")
config.byond_account_age_threshold = text2num(value)
// Discord stuff
if("enable_discord_webhooks")
discord_webhooks_enabled = TRUE
if("discord_webhooks_admin_role_id")
discord_admin_role_id = "[value]" // This MUST be a string because BYOND doesnt like massive integers
if("discord_webhooks_main_url")
discord_main_webhook_url = value
if("discord_webhooks_admin_url")
discord_admin_webhook_url = value
if("discord_webhooks_mentor_url")
discord_mentor_webhook_url = value
if("discord_forward_all_ahelps")
discord_forward_all_ahelps = TRUE
// End discord stuff
else
log_config("Unknown setting in configuration: '[name]'")
+82
View File
@@ -0,0 +1,82 @@
SUBSYSTEM_DEF(discord)
name = "Discord"
flags = SS_NO_FIRE
/// Is the SS enabled
var/enabled = FALSE
/// Last time the administrator ping was dropped. This ensures administrators cannot be mass pinged if a large chunk of ahelps go off at once (IE: tesloose)
var/last_administration_ping = 0
/datum/controller/subsystem/discord/Initialize(start_timeofday)
if(config.discord_webhooks_enabled)
enabled = TRUE
return ..()
// This is designed for ease of simplicity for sending quick messages from parts of the code
/datum/controller/subsystem/discord/proc/send2discord_simple(destination, content)
if(!enabled)
return
var/webhook_url
switch(destination)
if(DISCORD_WEBHOOK_ADMIN)
webhook_url = config.discord_admin_webhook_url
if(DISCORD_WEBHOOK_PRIMARY)
webhook_url = config.discord_main_webhook_url
if(DISCORD_WEBHOOK_MENTOR)
webhook_url = config.discord_mentor_webhook_url
var/datum/discord_webhook_payload/dwp = new()
dwp.webhook_content = content
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, webhook_url, dwp.serialize2json(), list("content-type" = "application/json"))
// This one is designed to take in a [/datum/discord_webhook_payload] which was prepared beforehand
/datum/controller/subsystem/discord/proc/send2discord_complex(destination, datum/discord_webhook_payload/dwp)
if(!enabled)
return
var/webhook_url
switch(destination)
if(DISCORD_WEBHOOK_ADMIN)
webhook_url = config.discord_admin_webhook_url
if(DISCORD_WEBHOOK_PRIMARY)
webhook_url = config.discord_main_webhook_url
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, webhook_url, dwp.serialize2json(), list("content-type" = "application/json"))
// This one is for sending messages to the admin channel if no admins are active, complete with a ping to the game admins role
/datum/controller/subsystem/discord/proc/send2discord_simple_noadmins(content, check_send_always = FALSE)
// Setup some stuff
var/alerttext
var/list/admincounter = staff_countup(R_BAN)
var/active_admins = admincounter[1]
var/inactive_admins = admincounter[3]
var/add_ping = TRUE
if(active_admins <= 0)
if(inactive_admins > 0)
alerttext = " | **ALL ADMINS AFK**"
else
alerttext = " | **NO ADMINS ONLINE**"
else
if(check_send_always && config.discord_forward_all_ahelps)
// If we are here, there are admins online. We want to forward everything, but obviously dont want to add a ping, so we do this
add_ping = FALSE
else
// We have active admins, we dont care about the rest of this proc
return
var/message = "[content] [alerttext] [add_ping ? handle_administrator_ping() : ""]"
var/datum/discord_webhook_payload/dwp = new()
dwp.webhook_content = message
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, config.discord_admin_webhook_url, dwp.serialize2json(), list("content-type" = "application/json"))
// Helper to make administrator ping easier
/datum/controller/subsystem/discord/proc/handle_administrator_ping()
// Check if a role is even set
if(config.discord_admin_role_id)
if(last_administration_ping > world.time)
return "*(Role pinged recently)*"
last_administration_ping = world.time + 60 SECONDS
return "<@&[config.discord_admin_role_id]>"
return "*(Role not configured)*"
+120
View File
@@ -0,0 +1,120 @@
SUBSYSTEM_DEF(http)
name = "HTTP"
flags = SS_TICKER | SS_BACKGROUND // Measure in ticks, but also only run if we have the spare CPU. We also dont init.
wait = 1
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // All the time
// Assuming for the worst, since only discord is hooked into this for now, but that may change
offline_implications = "The server is no longer capable of making async HTTP requests. Shuttle call recommended."
/// List of all async HTTP requests in the processing chain
var/list/datum/http_request/active_async_requests
/// Variable to define if logging is enabled or not. Disabled by default since we know the requests the server is making. Enable with VV if you need to debug requests
var/logging_enabled = FALSE
/datum/controller/subsystem/http/Initialize(start_timeofday)
rustg_create_async_http_client() // Open the door
active_async_requests = list()
return ..()
/datum/controller/subsystem/http/fire(resumed)
for(var/r in active_async_requests)
var/datum/http_request/req = r
// Check if we are complete
if(req.is_complete())
// If so, take it out the processing list
active_async_requests -= req
var/datum/http_response/res = req.into_response()
// If the request has a callback, invoke it.Async of course to avoid choking the SS
if(req.cb)
req.cb.InvokeAsync(res)
// And log the result
if(logging_enabled)
var/list/log_data = list()
log_data += "BEGIN ASYNC RESPONSE (ID: [req.id])"
if(res.errored)
log_data += "\t ----- RESPONSE ERRROR -----"
log_data += "\t [res.error]"
else
log_data += "\tResponse status code: [res.status_code]"
log_data += "\tResponse body: [res.body]"
log_data += "\tResponse headers: [json_encode(res.headers)]"
log_data += "END ASYNC RESPONSE (ID: [req.id])"
rustg_log_write(GLOB.http_log, log_data.Join("\n[GLOB.log_end]"))
/**
* Async request creator
*
* Generates an async request, and adds it to the subsystem's processing list
* These should be used as they do not lock the entire DD process up as they execute inside their own thread pool inside RUSTG
*/
/datum/controller/subsystem/http/proc/create_async_request(method, url, body = "", list/headers, datum/callback/proc_callback)
var/datum/http_request/req = new()
req.prepare(method, url, body, headers)
if(proc_callback)
req.cb = proc_callback
// Begin it and add it to the SS active list
req.begin_async()
active_async_requests += req
if(logging_enabled)
// Create a log holder
var/list/log_data = list()
log_data += "BEGIN ASYNC REQUEST (ID: [req.id])"
log_data += "\t[uppertext(req.method)] [req.url]"
log_data += "\tRequest body: [req.body]"
log_data += "\tRequest headers: [req.headers]"
log_data += "END ASYNC REQUEST (ID: [req.id])"
// Write the log data
rustg_log_write(GLOB.http_log, log_data.Join("\n[GLOB.log_end]"))
/**
* Blocking request creator
*
* Generates a blocking request, executes it, logs the info then cleanly returns the response
* Exists as a proof of concept, and should never be used
*/
/datum/controller/subsystem/http/proc/make_blocking_request(method, url, body = "", list/headers)
CRASH("Attempted use of a blocking HTTP request")
/*
var/datum/http_request/req = new()
req.prepare(method, url, body, headers)
req.execute_blocking()
var/datum/http_response/res = req.into_response()
// Now generate a logfile
var/list/log_data = list()
log_data += "NEW BLOCKING REQUEST"
log_data += "\t[uppertext(req.method)] [req.url]"
log_data += "\tRequest body: [req.body]"
log_data += "\tRequest headers: [req.headers]"
if(res.errored)
log_data += "\t ----- RESPONSE ERRROR -----"
log_data += "\t [res.error]"
else
log_data += "\tResponse status code: [res.status_code]"
log_data += "\tResponse body: [res.body]"
log_data += "\tResponse headers: [json_encode(res.headers)]"
log_data += "END BLOCKING REQUEST"
// Write the log data
rustg_log_write(GLOB.http_log, log_data.Join("\n[GLOB.log_end]"))
return res
*/
/*
Example of how to use callbacks properly
/client/verb/testing()
set name = "Testing"
var/datum/callback/cb = CALLBACK(src, /client/.proc/response, usr)
SShttp.create_async_request(RUSTG_HTTP_METHOD_GET, "http://site.domain/page.html", proc_callback=cb)
/client/proc/response(mob/user, datum/http_response/response)
to_chat(user, "<span class='notice'>Code: [response.status_code] | Content: [response.body]")
*/
+1 -3
View File
@@ -276,9 +276,7 @@ SUBSYSTEM_DEF(ticker)
//start_events() //handles random events and space dust.
//new random event system is handled from the MC.
var/list/admins_number = staff_countup(R_BAN)
if(admins_number[1] == 0 && admins_number[3] == 0)
send2irc(config.admin_notify_irc, "Round has started with no admins online.")
SSdiscord.send2discord_simple_noadmins("Round has started")
auto_toggle_ooc(0) // Turn it off
round_start_time = world.time