mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-28 22:47:59 +01:00
Small MC refactor (#20017)
* Small MC refactor * Order fix * Nabs tgstation/tgstation#27324 * Oops * gnarg
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
GLOBAL_DATUM_INIT(alarm_manager, /datum/alarm_manager, new())
|
||||
|
||||
/datum/alarm_manager
|
||||
var/list/alarms = list(
|
||||
"Motion" = list(),
|
||||
"Fire" = list(),
|
||||
"Atmosphere" = list(),
|
||||
"Power" = list(),
|
||||
"Burglar" = list()
|
||||
)
|
||||
|
||||
/datum/alarm_manager/proc/trigger_alarm(class, area/A, list/O, obj/alarmsource)
|
||||
var/list/L = alarms[class]
|
||||
for(var/I in L)
|
||||
if(I == A.name)
|
||||
var/list/alarm = L[I]
|
||||
var/list/sources = alarm[3]
|
||||
if(!(alarmsource.UID() in sources))
|
||||
sources += alarmsource.UID()
|
||||
return TRUE
|
||||
L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID()))
|
||||
SEND_SIGNAL(GLOB.alarm_manager, COMSIG_TRIGGERED_ALARM, class, A, O, alarmsource)
|
||||
return TRUE
|
||||
|
||||
/datum/alarm_manager/proc/cancel_alarm(class, area/A, obj/origin)
|
||||
var/list/L = alarms[class]
|
||||
var/cleared = FALSE
|
||||
for(var/I in L)
|
||||
if(I == A.name)
|
||||
var/list/alarm = L[I]
|
||||
var/list/srcs = alarm[3]
|
||||
srcs -= origin.UID()
|
||||
if(!length(srcs))
|
||||
cleared = TRUE
|
||||
L -= I
|
||||
|
||||
SEND_SIGNAL(GLOB.alarm_manager, COMSIG_CANCELLED_ALARM, class, A, origin, cleared)
|
||||
@@ -0,0 +1,114 @@
|
||||
GLOBAL_DATUM_INIT(discord_manager, /datum/discord_manager, new())
|
||||
|
||||
/datum/discord_manager
|
||||
/// 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
|
||||
/// Last time the mentor ping was dropped. This ensures mentors cannot be mass pinged if a large chunk of mhelps go off at once.
|
||||
var/last_mentor_ping = 0
|
||||
|
||||
// This is designed for ease of simplicity for sending quick messages from parts of the code
|
||||
/datum/discord_manager/proc/send2discord_simple(destination, content)
|
||||
if(!GLOB.configuration.discord.webhooks_enabled)
|
||||
return
|
||||
var/list/webhook_urls
|
||||
switch(destination)
|
||||
if(DISCORD_WEBHOOK_ADMIN)
|
||||
webhook_urls = GLOB.configuration.discord.admin_webhook_urls
|
||||
if(DISCORD_WEBHOOK_PRIMARY)
|
||||
webhook_urls = GLOB.configuration.discord.main_webhook_urls
|
||||
if(DISCORD_WEBHOOK_MENTOR)
|
||||
webhook_urls = GLOB.configuration.discord.mentor_webhook_urls
|
||||
|
||||
var/datum/discord_webhook_payload/dwp = new()
|
||||
dwp.webhook_content = "**\[[GLOB.configuration.system.instance_id]]** [content]"
|
||||
for(var/url in webhook_urls)
|
||||
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, 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/discord_manager/proc/send2discord_complex(destination, datum/discord_webhook_payload/dwp)
|
||||
if(!GLOB.configuration.discord.webhooks_enabled)
|
||||
return
|
||||
var/list/webhook_urls
|
||||
switch(destination)
|
||||
if(DISCORD_WEBHOOK_ADMIN)
|
||||
webhook_urls = GLOB.configuration.discord.admin_webhook_urls
|
||||
if(DISCORD_WEBHOOK_PRIMARY)
|
||||
webhook_urls = GLOB.configuration.discord.main_webhook_urls
|
||||
if(DISCORD_WEBHOOK_MENTOR)
|
||||
webhook_urls = GLOB.configuration.discord.mentor_webhook_urls
|
||||
for(var/url in webhook_urls)
|
||||
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, 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/discord_manager/proc/send2discord_simple_noadmins(content, check_send_always = FALSE)
|
||||
if(!GLOB.configuration.discord.webhooks_enabled)
|
||||
return
|
||||
// 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 && GLOB.configuration.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 = "**\[[GLOB.configuration.system.instance_id]]** [message]"
|
||||
for(var/url in GLOB.configuration.discord.admin_webhook_urls)
|
||||
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, url, dwp.serialize2json(), list("content-type" = "application/json"))
|
||||
|
||||
/datum/discord_manager/proc/send2discord_simple_mentor(content)
|
||||
var/alerttext
|
||||
var/list/mentorcounter = staff_countup(R_MENTOR)
|
||||
var/active_mentors = mentorcounter[1]
|
||||
var/inactive_mentors = mentorcounter[3]
|
||||
var/add_ping = FALSE
|
||||
|
||||
if(active_mentors <= 0)
|
||||
add_ping = TRUE
|
||||
if(inactive_mentors)
|
||||
alerttext = "| **ALL MENTORS AFK**"
|
||||
else
|
||||
alerttext = "| **NO MENTORS ONLINE**"
|
||||
|
||||
var/message = "[content] [alerttext][add_ping ? handle_mentor_ping() : ""]"
|
||||
|
||||
var/datum/discord_webhook_payload/dwp = new()
|
||||
dwp.webhook_content = "**\[[GLOB.configuration.system.instance_id]]** [message]"
|
||||
for(var/url in GLOB.configuration.discord.mentor_webhook_urls)
|
||||
SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, url, dwp.serialize2json(), list("content-type" = "application/json"))
|
||||
|
||||
// Helper to make administrator ping easier
|
||||
/datum/discord_manager/proc/handle_administrator_ping()
|
||||
// Check if a role is even set
|
||||
if(GLOB.configuration.discord.admin_role_id)
|
||||
if(last_administration_ping > world.time)
|
||||
return "*(Role pinged recently)*"
|
||||
|
||||
last_administration_ping = world.time + 60 SECONDS
|
||||
return "<@&[GLOB.configuration.discord.admin_role_id]>"
|
||||
|
||||
return ""
|
||||
|
||||
/datum/discord_manager/proc/handle_mentor_ping()
|
||||
if(GLOB.configuration.discord.mentor_role_id)
|
||||
if(last_mentor_ping > world.time)
|
||||
return " *(Role pinged recently)*"
|
||||
|
||||
last_mentor_ping = world.time + 60 SECONDS
|
||||
return " <@&[GLOB.configuration.discord.mentor_role_id]>"
|
||||
|
||||
return ""
|
||||
@@ -0,0 +1,365 @@
|
||||
GLOBAL_DATUM_INIT(ipintel_manager, /datum/ipintel_manager, new())
|
||||
|
||||
/datum/ipintel_manager
|
||||
var/throttle = 0
|
||||
var/errors = 0
|
||||
|
||||
var/list/cache = list()
|
||||
|
||||
// Represents an IP intel holder datum
|
||||
/datum/ipintel
|
||||
/// The IP being checked
|
||||
var/ip
|
||||
/// The current rating, 0-1 float.
|
||||
var/intel = 0
|
||||
/// Whether this was loaded from the cache or not
|
||||
var/cache = FALSE
|
||||
/// How many minutes ago it was cached
|
||||
var/cacheminutesago = 0
|
||||
/// The date it was cached
|
||||
var/cachedate = ""
|
||||
/// The real time it was cached
|
||||
var/cacherealtime = 0
|
||||
|
||||
/datum/ipintel/New()
|
||||
cachedate = SQLtime()
|
||||
cacherealtime = world.realtime
|
||||
|
||||
/datum/ipintel/proc/is_valid()
|
||||
. = FALSE
|
||||
if(intel < 0)
|
||||
return
|
||||
if(intel <= GLOB.configuration.ipintel.bad_rating)
|
||||
if(world.realtime < cacherealtime + (GLOB.configuration.ipintel.hours_save_good HOURS))
|
||||
return TRUE
|
||||
else
|
||||
if(world.realtime < cacherealtime + (GLOB.configuration.ipintel.hours_save_bad HOURS))
|
||||
return TRUE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get IP intel
|
||||
*
|
||||
* Performs a lookup of the rating for an IP provided
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP to lookup
|
||||
* * bypasscache - Do we want to bypass the DB cache?
|
||||
* * updatecache - Do we want to update the DB cache?
|
||||
*/
|
||||
/datum/ipintel_manager/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE)
|
||||
var/datum/ipintel/res = new()
|
||||
res.ip = ip
|
||||
. = res
|
||||
if(!ip || !GLOB.configuration.ipintel.contact_email || !GLOB.configuration.ipintel.enabled)
|
||||
return
|
||||
if(!bypasscache)
|
||||
var/datum/ipintel/cachedintel = cache[ip]
|
||||
if(cachedintel && cachedintel.is_valid())
|
||||
cachedintel.cache = TRUE
|
||||
return cachedintel
|
||||
|
||||
if(SSdbcore.IsConnected())
|
||||
var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({"
|
||||
SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW())
|
||||
FROM ipintel
|
||||
WHERE
|
||||
ip = INET_ATON(:ip)
|
||||
AND ((
|
||||
intel < :rating_bad
|
||||
AND
|
||||
date + INTERVAL :save_good HOUR > NOW()
|
||||
) OR (
|
||||
intel >= :rating_bad
|
||||
AND
|
||||
date + INTERVAL :save_bad HOUR > NOW()
|
||||
))
|
||||
"}, list(
|
||||
"ip" = ip,
|
||||
"rating_bad" = GLOB.configuration.ipintel.bad_rating,
|
||||
"save_good" = GLOB.configuration.ipintel.hours_save_good,
|
||||
"save_bad" = GLOB.configuration.ipintel.hours_save_bad,
|
||||
))
|
||||
if(!query_get_ip_intel.warn_execute())
|
||||
qdel(query_get_ip_intel)
|
||||
return
|
||||
if(query_get_ip_intel.NextRow())
|
||||
res.cache = TRUE
|
||||
res.cachedate = query_get_ip_intel.item[1]
|
||||
res.intel = text2num(query_get_ip_intel.item[2])
|
||||
res.cacheminutesago = text2num(query_get_ip_intel.item[3])
|
||||
res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60)
|
||||
cache[ip] = res
|
||||
qdel(query_get_ip_intel)
|
||||
return
|
||||
qdel(query_get_ip_intel)
|
||||
res.intel = ip_intel_query(ip)
|
||||
if(updatecache && res.intel >= 0)
|
||||
cache[ip] = res
|
||||
if(SSdbcore.IsConnected())
|
||||
var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery({"
|
||||
INSERT INTO ipintel (ip, intel) VALUES (INET_ATON(:ip), :intel)
|
||||
ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()"},
|
||||
list(
|
||||
"ip" = ip,
|
||||
"intel" = res.intel
|
||||
)
|
||||
)
|
||||
query_add_ip_intel.warn_execute()
|
||||
qdel(query_add_ip_intel)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Performs the remote IPintel lookup
|
||||
*
|
||||
*
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP to lookup
|
||||
* * retried - Was this attempt retried?
|
||||
*/
|
||||
/datum/ipintel_manager/proc/ip_intel_query(ip, retried = FALSE)
|
||||
. = -1 //default
|
||||
if(!ip)
|
||||
return
|
||||
if(throttle > world.timeofday)
|
||||
return
|
||||
|
||||
// Do not refactor this to use SShttp, because that requires the subsystem to be firing for requests to be made, and this will be triggered before the MC has finished loading
|
||||
var/list/http[] = HTTPGet("http://[GLOB.configuration.ipintel.ipintel_domain]/check.php?ip=[ip]&contact=[GLOB.configuration.ipintel.contact_email]&format=json&flags=b")
|
||||
|
||||
if(http)
|
||||
var/status = text2num(http["STATUS"])
|
||||
|
||||
if(status == 200)
|
||||
var/response = json_decode(http["CONTENT"])
|
||||
if(response)
|
||||
if(response["status"] == "success")
|
||||
var/intelnum = text2num(response["result"])
|
||||
if(isnum(intelnum))
|
||||
return text2num(response["result"])
|
||||
else
|
||||
ipintel_handle_error("Bad intel from server: [response["result"]].", ip, retried)
|
||||
if(!retried)
|
||||
sleep(25)
|
||||
return .(ip, 1)
|
||||
else
|
||||
ipintel_handle_error("Bad response from server: [response["status"]].", ip, retried)
|
||||
if(!retried)
|
||||
sleep(25)
|
||||
return .(ip, 1)
|
||||
|
||||
else if(status == 429)
|
||||
ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1)
|
||||
return
|
||||
else
|
||||
ipintel_handle_error("Unknown status code: [status].", ip, retried)
|
||||
if(!retried)
|
||||
sleep(25)
|
||||
return .(ip, 1)
|
||||
else
|
||||
ipintel_handle_error("Unable to connect to API.", ip, retried)
|
||||
if(!retried)
|
||||
sleep(25)
|
||||
return .(ip, 1)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error handler
|
||||
*
|
||||
* Handles an IP intel error, also throttling the susbystem if required
|
||||
*
|
||||
* Arguments:
|
||||
* * error - The error description
|
||||
* * ip - The IP that was tried
|
||||
* * retried - Was this on a retried attempt
|
||||
*/
|
||||
/datum/ipintel_manager/proc/ipintel_handle_error(error, ip, retried)
|
||||
if(retried)
|
||||
errors++
|
||||
error += " Could not check [ip]. Disabling IPINTEL for [errors] minute[(errors == 1 ? "" : "s")]"
|
||||
throttle = world.timeofday + (2 * errors MINUTES)
|
||||
else
|
||||
error += " Attempting retry on [ip]."
|
||||
log_ipintel(error)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Logs an IPintel error
|
||||
*
|
||||
* Pretty self explanatory. Logs errors regarding ipintel.
|
||||
*
|
||||
* Arguments:
|
||||
* * text - Argument 1
|
||||
*/
|
||||
/datum/ipintel_manager/proc/log_ipintel(text)
|
||||
log_game("IPINTEL: [text]")
|
||||
log_debug("IPINTEL: [text]")
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* IPIntel Ban Checker
|
||||
*
|
||||
* Checks if a user is banned due to IPintel. It will check configuration, DB, whitelist checks, and more
|
||||
*
|
||||
* Arguments:
|
||||
* * t_ckey - The ckey to check
|
||||
* * t_ip - The IP to check
|
||||
*/
|
||||
/datum/ipintel_manager/proc/ipintel_is_banned(t_ckey, t_ip)
|
||||
if(!GLOB.configuration.ipintel.contact_email)
|
||||
return FALSE
|
||||
if(!GLOB.configuration.ipintel.enabled)
|
||||
return FALSE
|
||||
if(!GLOB.configuration.ipintel.whitelist_mode)
|
||||
return FALSE
|
||||
if(!SSdbcore.IsConnected())
|
||||
return FALSE
|
||||
if(!ipintel_badip_check(t_ip))
|
||||
return FALSE
|
||||
if(vpn_whitelist_check(t_ckey))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* IP Rating Checker
|
||||
*
|
||||
* Checks if a provided IP passes the config threshold for denial
|
||||
*
|
||||
* Arguments:
|
||||
* * target_ip - The IP to check
|
||||
*/
|
||||
/datum/ipintel_manager/proc/ipintel_badip_check(target_ip)
|
||||
var/rating_bad = GLOB.configuration.ipintel.bad_rating
|
||||
if(!rating_bad)
|
||||
log_debug("ipintel_badip_check reports misconfigured rating_bad directive")
|
||||
return FALSE
|
||||
var/valid_hours = GLOB.configuration.ipintel.hours_save_bad
|
||||
if(!valid_hours)
|
||||
log_debug("ipintel_badip_check reports misconfigured ipintel_save_bad directive")
|
||||
return FALSE
|
||||
var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({"
|
||||
SELECT * FROM ipintel WHERE ip = INET_ATON(:target_ip)
|
||||
AND intel >= :rating_bad AND (date + INTERVAL :valid_hours HOUR) > NOW()"},
|
||||
list(
|
||||
"target_ip" = target_ip,
|
||||
"rating_bad" = rating_bad,
|
||||
"valid_hours" = valid_hours
|
||||
)
|
||||
)
|
||||
if(!query_get_ip_intel.warn_execute())
|
||||
log_debug("ipintel_badip_check reports failed query execution")
|
||||
qdel(query_get_ip_intel)
|
||||
return FALSE
|
||||
if(!query_get_ip_intel.NextRow())
|
||||
qdel(query_get_ip_intel)
|
||||
return FALSE
|
||||
qdel(query_get_ip_intel)
|
||||
return TRUE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* VPN whitelist checker
|
||||
*
|
||||
* Checks if a ckey is whitelisted to be using a VPN against the DB
|
||||
*
|
||||
* Arguments:
|
||||
* * target_ckey - The ckey to check
|
||||
*/
|
||||
/datum/ipintel_manager/proc/vpn_whitelist_check(target_ckey)
|
||||
if(!GLOB.configuration.ipintel.whitelist_mode)
|
||||
return FALSE
|
||||
var/datum/db_query/query_whitelist_check = SSdbcore.NewQuery("SELECT * FROM vpn_whitelist WHERE ckey=:ckey", list(
|
||||
"ckey" = target_ckey
|
||||
))
|
||||
if(!query_whitelist_check.warn_execute())
|
||||
qdel(query_whitelist_check)
|
||||
return FALSE
|
||||
if(query_whitelist_check.NextRow())
|
||||
qdel(query_whitelist_check)
|
||||
return TRUE // At least one row in the whitelist names their ckey. That means they are whitelisted.
|
||||
qdel(query_whitelist_check)
|
||||
return FALSE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* VPN whitelist adder
|
||||
*
|
||||
* Adds a ckey to the VPN whitelist. Asks the admin to also provide a link to their request.
|
||||
*
|
||||
* Arguments:
|
||||
* * target_ckey - The ckey to whitelist
|
||||
*/
|
||||
/datum/ipintel_manager/proc/vpn_whitelist_add(target_ckey)
|
||||
var/reason_string = input(usr, "Enter link to the URL of their whitelist request on the forum.","Reason required") as message|null
|
||||
if(!reason_string)
|
||||
return FALSE
|
||||
var/datum/db_query/query_whitelist_add = SSdbcore.NewQuery("INSERT INTO vpn_whitelist (ckey,reason) VALUES (:targetckey, :reason)", list(
|
||||
"targetckey" = target_ckey,
|
||||
"reason" = reason_string
|
||||
))
|
||||
if(!query_whitelist_add.warn_execute())
|
||||
qdel(query_whitelist_add)
|
||||
return FALSE
|
||||
qdel(query_whitelist_add)
|
||||
return TRUE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* VPN whitelist remover
|
||||
*
|
||||
* Removes a ckey from the VPN whitelist. Pretty simple.
|
||||
*
|
||||
* Arguments:
|
||||
* * target_ckey - The ckey to remove
|
||||
*/
|
||||
/datum/ipintel_manager/proc/vpn_whitelist_remove(target_ckey)
|
||||
var/datum/db_query/query_whitelist_remove = SSdbcore.NewQuery("DELETE FROM vpn_whitelist WHERE ckey=:targetckey", list(
|
||||
"targetckey" = target_ckey
|
||||
))
|
||||
if(!query_whitelist_remove.warn_execute())
|
||||
qdel(query_whitelist_remove)
|
||||
return FALSE
|
||||
qdel(query_whitelist_remove)
|
||||
return TRUE
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* VPN whitelist panel
|
||||
*
|
||||
* Doesnt actually open a panel, this is just a verb to handle the rest of the whitelist operations
|
||||
*
|
||||
* Arguments:
|
||||
* * target_ckey - The ckey to add/remove
|
||||
*/
|
||||
/datum/ipintel_manager/proc/vpn_whitelist_panel(target_ckey as text)
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
if(!target_ckey)
|
||||
return
|
||||
var/is_already_whitelisted = vpn_whitelist_check(target_ckey)
|
||||
if(is_already_whitelisted)
|
||||
var/confirm = alert("[target_ckey] is already whitelisted. Remove them?", "Confirm Removal", "No", "Yes")
|
||||
if(!confirm || confirm != "Yes")
|
||||
to_chat(usr, "VPN whitelist alteration cancelled.")
|
||||
return
|
||||
else if(vpn_whitelist_remove(target_ckey))
|
||||
to_chat(usr, "[target_ckey] was removed from the VPN whitelist.")
|
||||
else
|
||||
to_chat(usr, "VPN whitelist unchanged.")
|
||||
else
|
||||
if(vpn_whitelist_add(target_ckey))
|
||||
to_chat(usr, "[target_ckey] was added to the VPN whitelist.")
|
||||
else
|
||||
to_chat(usr, "VPN whitelist unchanged.")
|
||||
Reference in New Issue
Block a user