Files
Bubberstation/code/modules/admin/verbs/adminpm.dm
san7890 ccef887efe Lints Against Unmanaged Local Defines (#74333)
# MAINTAINER - USE THE BUTTON THAT SAYS "MERGE MASTER" THEN SET THE PR
TO AUTO-MERGE! IT'S MUCH EASIER FOR ME TO FIX THINGS BEFORE THEY SKEW
RATHER THAN AFTER THE FACT.

## About The Pull Request

Hey there,

This took a while to do, but here's the gist:

Python file now regexes every file in `/code` except for those that have
some valid reason to be tacking on more global defines. Some of those
reasons are simply just that I don't have the time right now (doing what
you see in this PR took a few hours) to refactor and parse what should
belong and what should be thrown out. For the time being though, this PR
will at least _halt_ people making the mistake of not `#undef`ing any
files they `#define` "locally", or within the scope of a file.

Most people forget to do this and this leads to a lot of mess later on
due to how many variables can be unmanaged on the global level. I've
made this mistake, you've made this mistake, it's a common thing. Let's
automatically check for it so it can be fixed no-stress.

Scenarios this PR corrects:

* Forgetting to undef a define but undeffing others.
* Not undeffing any defines in your file.
* Earmarking a define as a "file local" define, but not defining it.
* Having a define be a "file local" define, but having it be used
elsewhere.
* Having a "local" define not even be in the file that it only shows up
in.
* Having a completely unused define*

(* I kept some of these because they seemed important... Others were
junked.)
## Why It's Good For The Game

If you wanna use it across multiple files, no reason to not make it a
global define (maybe there's a few reasons but let's assume that this is
the 95% case).

Let me know if you don't like how I re-arranged some of the defines and
how you'd rather see it be implemented, and I'd be happy to do that.
This was mostly just "eh does it need it or not" sorta stuff.

I used a pretty cool way to detect if we should use the standardized
GitHub "error" output, you can see the results of that here
https://github.com/san7890/bruhstation/actions/runs/4549766579/jobs/8022186846#step:7:792
## Changelog
Nothing that really concerns players.

(I fixed up all this stuff using vscode, no regexes beyond what you see
in the python script. sorry downstreams)
2023-03-29 10:17:03 -07:00

797 lines
30 KiB
Plaintext

#define EXTERNALREPLYCOUNT 2
#define EXTERNAL_PM_USER "IRCKEY"
// HEY FUCKO, IMPORTANT NOTE!
// This file, and pretty much everything that directly handles ahelps, is VERY important
// An admin pm dropping by coding error is disastorus, because it gives no feedback to admins, so they think they're being ignored
// It is imparitive that this does not happen. Therefore, runtimes are not allowed in this file
// Additionally, any runtimes here would cause admin tickets to leak into the runtime logs
// This is less of a big deal, but still bad
//
// In service of this goal of NO RUNTIMES then, we make ABSOLUTELY sure to never trust the nullness of a value
// That's why variables are so separated from logic here. It's not a good pattern typically, but it helps make assumptions clear here
// We also make SURE to fail loud, IE: if something stops the message from reaching the recipient, the sender HAS to know
// If you "refactor" this to make it "cleaner" I will send you to hell
/// Allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm
/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list)
set category = null
set name = "Admin PM Mob"
if(!holder)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Context: Only administrators may use this command."),
confidential = TRUE)
return
if(!ismob(M))
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Context: Target mob is not a mob, somehow."),
confidential = TRUE)
return
cmd_admin_pm(M.client, null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/// Shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm
/client/proc/cmd_admin_pm_panel()
set category = "Admin"
set name = "Admin PM"
if(!holder)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Panel: Only administrators may use this command."),
confidential = TRUE)
return
var/list/targets = list()
for(var/client/client in GLOB.clients)
var/nametag = ""
var/mob/lad = client.mob
var/mob_name = lad?.name
var/real_mob_name = lad?.real_name
if(!lad)
nametag = "(No Mob)"
else if(isnewplayer(lad))
nametag = "(New Player)"
else if(isobserver(lad))
nametag = "[mob_name](Ghost)"
else
nametag = "[real_mob_name](as [mob_name])"
targets["[nametag] - [client]"] = client
var/target = input(src,"To whom shall we send a message?", "Admin PM", null) as null|anything in sort_list(targets)
if (isnull(target))
return
cmd_admin_pm(targets[target], null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/// Replys to some existing ahelp, reply to whom, which can be a client or ckey
/client/proc/cmd_ahelp_reply(whom)
if(IsAdminAdvancedProcCall())
return FALSE
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Reply: You are unable to use admin PM-s (muted)."),
confidential = TRUE)
return
// We use the ckey here rather then keeping the client to ensure resistance to client logouts mid execution
if(istype(whom, /client))
var/client/boi = whom
whom = boi.ckey
var/ambiguious_recipient = disambiguate_client(whom)
if(!istype(ambiguious_recipient, /client))
if(holder)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Reply: Client not found."),
confidential = TRUE)
return
// Existing client case
var/client/recipient = ambiguious_recipient
// The ticket our recipient is using
var/datum/admin_help/recipient_ticket = recipient?.current_ticket
// Any past interactions with the recipient ticket
var/datum/admin_help/recipient_interactions = recipient_ticket?.ticket_interactions
// Any opening interactions with the recipient ticket, IE: interactions started before the ticket first recieves a response
var/datum/admin_help/opening_interactions = recipient_ticket?.opening_responders
// Our recipient's admin holder, if one exists
var/datum/admins/recipient_holder = recipient?.holder
// The ckey of our recipient
var/recipient_ckey = recipient?.ckey
// Our recipient's fake key, if they are faking their ckey
var/recipient_fake_key = recipient_holder?.fakekey
// Our ckey, with our mob's name if one exists, formatted with a reply link
var/our_linked_name = key_name_admin(src)
// The recipient's ckey, formatted with a reply link
var/recipient_linked_ckey = key_name_admin(recipient, FALSE)
// The recipient's ckey, formatted slightly with html
var/formatted_recipient_ckey = key_name(recipient, FALSE, FALSE)
var/message_prompt = "Message:"
if(recipient_ticket)
message_admins("[our_linked_name] has started replying to [recipient_linked_ckey]'s admin help.")
// If none's interacted with the ticket yet
if(length(recipient_interactions) == 1)
if(length(opening_interactions)) // Inform the admin that they aren't the first
var/printable_interators = english_list(opening_interactions)
SEND_SOUND(src, sound('sound/machines/buzz-sigh.ogg', volume=30))
message_prompt += "\n\n**This ticket is already being responded to by: [printable_interators]**"
// add the admin who is currently responding to the list of people responding
LAZYADD(recipient_ticket.opening_responders, src)
var/request = "Private message to"
if(recipient_fake_key)
request = "[request] an Administrator."
else
request = "[request] [formatted_recipient_ckey]."
var/message = input(src, message_prompt, request) as message|null
if(recipient_ticket)
LAZYREMOVE(recipient_ticket.opening_responders, src)
if (!message)
message_admins("[our_linked_name] has cancelled their reply to [recipient_linked_ckey]'s admin help.")
return
if(!recipient) //We lost the client during input, disconnected or relogged.
if(GLOB.directory[recipient_ckey]) // Client has reconnected, lets try to recover
whom = GLOB.directory[recipient_ckey]
else
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Reply: Client not found."),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = "[span_danger("<b>Message not sent:</b>")]<br>[message]",
confidential = TRUE)
if(recipient_ticket)
recipient_ticket.AddInteraction("<b>No client found, message not sent:</b><br>[message]")
return
cmd_admin_pm(whom, message)
//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed.
//whom here is a client, a ckey, or [EXTERNAL_PM_USER] if this is from tgs. message is the default message to send
/client/proc/cmd_admin_pm(whom, message)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM: You are unable to use admin PM-s (muted)."),
confidential = TRUE)
return
if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be."),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("Message: [message]"),
confidential = TRUE)
return
// We use the ckey here rather then keeping the client to ensure resistance to client logouts mid execution
if(istype(whom, /client))
var/client/boi = whom
whom = boi.ckey
var/message_to_send = request_adminpm_message(disambiguate_client(whom), message)
if(!message_to_send)
return
if(!sends_adminpm_message(disambiguate_client(whom), message_to_send))
return
notify_adminpm_message(disambiguate_client(whom), message_to_send)
/// Requests an admin pm message to send
/// message_target here can be either [EXTERNAL_PM_USER], indicating that this message is intended for some external chat channel
/// or a /client, which we will then store info about to ensure logout -> logins are protected as expected
/// Accepts an optional existing message, which will be used in place of asking the recipient assuming all other conditions are met
/// Returns the message to send or null if no message is found
/// Sleeps
/client/proc/request_adminpm_message(ambiguious_recipient, existing_message = null)
if(IsAdminAdvancedProcCall())
return null
if(ambiguious_recipient == EXTERNAL_PM_USER)
if(!externalreplyamount) //to prevent people from spamming irc/discord
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: External reply cap hit."),
confidential = TRUE)
return null
var/msg = ""
if(existing_message)
msg = existing_message
else
msg = input(src,"Message:", "Private message to Administrator") as message|null
if(!msg)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: No message input."),
confidential = TRUE)
return null
if(holder)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: Use the admin IRC/Discord channel, nerd."),
confidential = TRUE)
return null
return msg
if(!istype(ambiguious_recipient, /client))
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: Client not found."),
confidential = TRUE)
return null
var/client/recipient = ambiguious_recipient
// Stored in case client is deleted between this and after the message is input
var/recipient_ckey = recipient?.ckey
// Stored in case client is deleted between this and after the message is input
var/datum/admin_help/recipient_ticket = recipient?.current_ticket
// Our current active ticket
var/datum/admin_help/our_ticket = current_ticket
// If our recipient is an admin, this is their admins datum
var/datum/admins/recipient_holder = recipient?.holder
// If our recipient has a fake name, this is it
var/recipient_fake_key = recipient_holder?.fakekey
// Just the recipient's ckey, formatted for htmlifying stuff
var/recipient_print_key = key_name(recipient, FALSE, FALSE)
// The message we intend on returning
var/msg = ""
if(existing_message)
msg = existing_message
else
var/request = "Private message to"
if(recipient_fake_key)
request = "[request] an Administrator."
else
request = "[request] [recipient_print_key]."
//get message text, limit it's length.and clean/escape html
msg = input(src,"Message:", request) as message|null
msg = trim(msg)
if(!msg)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: No message input."),
confidential = TRUE)
return null
if(recipient)
return msg
// Client has disappeared due to logout
if(GLOB.directory[recipient_ckey]) // Client has reconnected, lets try to recover
recipient = GLOB.directory[recipient_ckey]
return msg
// We don't tell standard users if a ticket drops because admins have a way to actually see
// Past tickets, and well, admins are the ones who might ban you if you ignore them
if(holder)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Message: Client not found."),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = "[span_danger("<b>Message not sent:</b>")]<br>[msg]",
confidential = TRUE)
if(recipient_ticket)
recipient_ticket.AddInteraction("<b>No client found, message not sent:</b><br>[msg]")
return null
if(our_ticket)
our_ticket.MessageNoRecipient(msg)
return null
/// Sends a pm message via the tickets system
/// message_target here can be either [EXTERNAL_PM_USER], indicating that this message is intended for some external chat channel
/// or a /client, in which case we send in the standard form
/// send_message is the raw message to send, it will be filtered and treated to ensure we do not break any text handling
/// Returns FALSE if the send failed, TRUE otherwise
/client/proc/sends_adminpm_message(ambiguious_recipient, send_message)
if(IsAdminAdvancedProcCall())
return FALSE
send_message = adminpm_filter_text(ambiguious_recipient, send_message)
if(!send_message)
return null
if (handle_spam_prevention(send_message, MUTE_ADMINHELP))
// handle_spam_prevention does its own "hey buddy ya fucker up here's what happen"
return FALSE
var/raw_message = send_message
if(holder)
send_message = emoji_parse(send_message)
var/keyword_parsed_msg = keywords_lookup(send_message)
// Stores a bit of html with our ckey, name, and a linkified string to click and rely to us with
var/name_key_with_link = key_name(src, TRUE, TRUE)
if(ambiguious_recipient == EXTERNAL_PM_USER)
var/datum/admin_help/new_admin_help = admin_ticket_log(src,
"<font color='red'>Reply PM from-<b>[name_key_with_link]</b> to <i>External</i>: [keyword_parsed_msg]</font>",
player_message = "<font color='red'>Reply PM from-<b>[name_key_with_link]</b> to <i>External</i>: [send_message]</font>")
new_admin_help.reply_to_admins_notification(raw_message)
var/new_help_id = new_admin_help?.id
externalreplyamount--
var/category = "Reply: [ckey]"
if(new_admin_help)
category = "#[new_help_id] [category]"
send2adminchat(category, raw_message)
return TRUE
if(!istype(ambiguious_recipient, /client))
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Send: Client not found."),
confidential = TRUE)
return FALSE
var/client/recipient = ambiguious_recipient
var/datum/admins/recipient_holder = recipient.holder
var/datum/admins/our_holder = holder
// The sound preferences of the recipient, at least that's how we'll be using it here
var/sound_prefs = recipient?.prefs?.toggles
// Stores a bit of html that contains the ckey of the recipient, its mob's name if any exist, and a link to reply to them with
var/their_name_with_link = key_name(recipient, TRUE, TRUE)
// Stores a bit of html with our ckey highlighted as a reply link
var/link_to_us = key_name(src, TRUE, FALSE)
// Stores a bit of html with outhe ckey of the recipientr highlighted as a reply link
var/link_to_their = key_name(recipient, TRUE, FALSE)
// Our ckey
var/our_ckey = ckey
// Recipient ckey
var/recip_ckey = recipient?.ckey
// Our current ticket, can (supposedly) be null here
var/datum/admin_help/ticket = current_ticket
// The recipient's current ticket, could in theory? maybe? be null here
var/datum/admin_help/recipient_ticket = recipient?.current_ticket
// I use -1 as a default for both of these
// Our ticket ID
var/ticket_id = ticket?.id
// The recipient's ticket id
var/recipient_ticket_id = recipient_ticket?.id
// If we should do a full on boink, so with the text and extra flair and everything
// We want to always do this so long as WE are an admin, and we're messaging the "loser" of the converstation
var/full_boink = FALSE
// Only admins can perform boinks
if(our_holder)
full_boink = TRUE
// Tickets will only generate for the non admin/admin being boinked. This check is to ensure boinked admins don't send the same
// ADMINISTRAITOR PRIVATE MESSAGE text to their boinker every time they respond
if(recipient_holder && ticket)
full_boink = FALSE
// If we're gonna boink em, do it now
// It is worth noting this will always generate the target a ticket if they don't already have one (tickets will generate if a player ahelps automatically, outside this logic)
// So past this point, because of our block above here, we can be reasonably guarenteed that the user will have a ticket
if(full_boink)
// Do BIG RED TEXT
var/already_logged = FALSE
// Full boinks will always be done to players, so we are not guarenteed that they won't have a ticket
if(!recipient_ticket)
new /datum/admin_help(send_message, recipient, TRUE)
already_logged = TRUE
// This action mutates our existing cached ticket information, so we recache
ticket = current_ticket
recipient_ticket = recipient?.current_ticket
ticket_id = ticket?.id
recipient_ticket_id = recipient_ticket?.id
SSblackbox.LogAhelp(recipient_ticket_id, "Ticket Opened", send_message, recipient.ckey, src.ckey)
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
html = "<font color='red' size='4'><b>-- Administrator private message --</b></font>",
confidential = TRUE)
recipient.receive_ahelp(
link_to_us,
span_linkify(send_message),
)
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
html = span_adminsay("<i>Click on the administrator's name to reply.</i>"),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("Admin PM to-<b>[their_name_with_link]</b>: [span_linkify(send_message)]"),
confidential = TRUE)
admin_ticket_log(recipient,
"<font color='purple'>PM From [name_key_with_link]: [keyword_parsed_msg]</font>",
log_in_blackbox = FALSE,
player_message = "<font color='purple'>PM From [link_to_us]: [send_message]</font>")
if(!already_logged) //Reply to an existing ticket
SSblackbox.LogAhelp(recipient_ticket_id, "Reply", send_message, recip_ckey, our_ckey)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
return TRUE
// Ok if we're here, either this message is for an admin, or someone somehow figured out how to send a new message as a player
// First case well, first
if(!our_holder && !recipient_holder) //neither are admins
if(!ticket)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Send: Non-admin to non-admin PM communication is forbidden."),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = "[span_danger("<b>Message not sent:</b>")]<br>[send_message]",
confidential = TRUE)
return FALSE
ticket.MessageNoRecipient(send_message)
return TRUE
// Ok by this point the recipient has to be an admin, and this is either an admin on admin event, or a player replying to an admin
// You're replying to a ticket that is closed. Bad move. You must have started replying before the close, and then got input()'d
// Lets be nice and pass this off to a new ticket, as we recomend above
if(!ticket)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Send: Attempted to send a reply to a closed ticket."),
confidential = TRUE)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("Relaying message to a new admin help."),
confidential = TRUE)
GLOB.admin_help_ui_handler.perform_adminhelp(src, raw_message, FALSE)
return FALSE
// Let's play some music for the admin, only if they want it tho
if(sound_prefs & SOUND_ADMINHELP)
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
SEND_SIGNAL(ticket, COMSIG_ADMIN_HELP_REPLIED)
// Admin on admin violence first
if(our_holder)
recipient.receive_ahelp(
name_key_with_link,
span_linkify(keyword_parsed_msg),
"danger",
)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("Admin PM to-<b>[their_name_with_link]</b>: [span_linkify(keyword_parsed_msg)]"),
confidential = TRUE)
//omg this is dumb, just fill in both their logs
var/interaction_message = "<font color='purple'>PM from-<b>[name_key_with_link]</b> to-<b>[their_name_with_link]</b>: [keyword_parsed_msg]</font>"
var/player_interaction_message = "<font color='purple'>PM from-<b>[link_to_us]</b> to-<b>[link_to_their]</b>: [send_message]</font>"
admin_ticket_log(src,
interaction_message,
log_in_blackbox = FALSE,
player_message = player_interaction_message)
if(recipient != src) //reeee
admin_ticket_log(recipient,
interaction_message,
log_in_blackbox = FALSE,
player_message = player_interaction_message)
SSblackbox.LogAhelp(ticket_id, "Reply", send_message, recip_ckey, our_ckey)
return TRUE
// This is us (a player) trying to talk to the recipient (an admin)
var/replymsg = "Reply PM from-<b>[name_key_with_link]</b>: [span_linkify(keyword_parsed_msg)]"
var/player_replymsg = "Reply PM from-<b>[link_to_us]</b>: [span_linkify(send_message)]"
admin_ticket_log(src,
"<font color='red'>[replymsg]</font>",
log_in_blackbox = FALSE,
player_message = player_replymsg)
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("[replymsg]"),
confidential = TRUE)
ticket.reply_to_admins_notification(send_message)
SSblackbox.LogAhelp(ticket_id, "Reply", send_message, recip_ckey, our_ckey)
return TRUE
/// Notifies all admins about the existance of an admin pm, then logs the pm
/// message_target here can be either [EXTERNAL_PM_USER], indicating that this message is intended for some external chat channel
/// or a /client, in which case we send in the standard form
/// log_message is the raw message to send, it will be filtered and treated to ensure we do not break any text handling
/client/proc/notify_adminpm_message(ambiguious_recipient, log_message)
if(IsAdminAdvancedProcCall())
return
// First we filter, because these procs can be called by anyone with debug, and I don't trust that check
// gotta make sure none's fucking about
log_message = adminpm_filter_text(ambiguious_recipient, log_message)
if(!log_message)
return
var/raw_message = log_message
if(holder)
log_message = emoji_parse(log_message)
var/keyword_parsed_msg = keywords_lookup(log_message)
// Shows our ckey and the name of any mob we might be possessing
var/our_name = key_name(src)
// Shows our ckey/name embedded inside a clickable link to reply to this message
var/our_linked_ckey = key_name(src, TRUE, FALSE)
// Our current active ticket
var/datum/admin_help/ticket = current_ticket
// Our current ticket id, if one exists
var/ticket_id = ticket?.id
if(ambiguious_recipient == EXTERNAL_PM_USER)
// Guard against the possibility of a null, since it'll runtime and spit out the contents of what should be a private ticket.
if(ticket)
log_admin_private("PM: Ticket #[ticket_id]: [our_name]->External: [sanitize_text(trim(raw_message))]")
else
log_admin_private("PM: [our_name]->External: [sanitize_text(trim(raw_message))]")
for(var/client/lad in GLOB.admins)
to_chat(lad,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("<B>PM: [our_linked_ckey]-&gt;External:</B> [keyword_parsed_msg]"),
confidential = TRUE)
return
if(!istype(ambiguious_recipient, /client))
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Notify: Client not found."),
confidential = TRUE)
return
var/client/recipient = ambiguious_recipient
// The key of our recipient
var/recipient_key = recipient?.key
// Shows the recipient's ckey and the name of any mob it might be possessing
var/recipient_name = key_name(recipient)
// Shows the recipient's ckey/name embedded inside a clickable link to reply to this message
var/recipient_linked_ckey = key_name(recipient, TRUE, FALSE)
window_flash(recipient, ignorepref = TRUE)
if(ticket)
log_admin_private("PM: Ticket #[ticket_id]: [our_name]->[recipient_name]: [sanitize_text(trim(raw_message))]")
else
log_admin_private("PM: [our_name]->[recipient_name]: [sanitize_text(trim(raw_message))]")
//we don't use message_admins here because the sender/receiver might get it too
for(var/client/lad in GLOB.admins)
if(lad.key == key || lad.key == recipient_key) //check to make sure client/lad isn't the sender or recipient
continue
to_chat(lad,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("<B>PM: [our_linked_ckey]-&gt;[recipient_linked_ckey]:</B> [keyword_parsed_msg]") ,
confidential = TRUE)
/// Accepts a message and an ambiguious recipient (some sort of client representative, or [EXTERNAL_PM_USER])
/// Returns the filtered message if it passes all checks, or null if the send fails
/client/proc/adminpm_filter_text(ambiguious_recipient, message)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Filter: You are unable to use admin PM-s (muted)."),
confidential = TRUE)
return
//clean the message if it's not sent by a high-rank admin
if(!check_rights(R_SERVER|R_DEBUG, 0) || ambiguious_recipient == EXTERNAL_PM_USER)//no sending html to the poor bots
message = sanitize(copytext_char(message, 1, MAX_MESSAGE_LEN))
if(!message)
to_chat(src,
type = MESSAGE_TYPE_ADMINPM,
html = span_danger("Error: Admin-PM-Filter: Your message contained only HTML, it's been sanitized away and the message disregarded."),
confidential = TRUE)
return
return message
#define TGS_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/TgsPm(target, message, sender)
var/requested_ckey = ckey(target)
var/ambiguious_target = disambiguate_client(requested_ckey)
var/client/recipient
// This might seem like hiding a failure condition, but we want to be able to send commands to the ticket without the client being logged in
if(istype(ambiguious_target, /client))
recipient = ambiguious_target
// The ticket we want to talk about here. Either the target's active ticket, or the last one it had
var/datum/admin_help/ticket
if(recipient)
ticket = recipient.current_ticket
else
GLOB.ahelp_tickets.CKey2ActiveTicket(requested_ckey)
// The ticket's id
var/ticket_id = ticket?.id
var/compliant_msg = trim(lowertext(message))
var/tgs_tagged = "[sender](TGS/External)"
var/list/splits = splittext(compliant_msg, " ")
var/split_size = length(splits)
if(split_size && splits[1] == "ticket")
if(split_size < 2)
return TGS_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
ticket.Close(tgs_tagged)
return "Ticket #[ticket_id] successfully closed"
if("resolve")
if(ticket)
ticket.Resolve(tgs_tagged)
return "Ticket #[ticket_id] successfully resolved"
if("icissue")
if(ticket)
ticket.ICIssue(tgs_tagged)
return "Ticket #[ticket_id] successfully marked as IC issue"
if("reject")
if(ticket)
ticket.Reject(tgs_tagged)
return "Ticket #[ticket_id] successfully rejected"
if("reopen")
if(ticket)
return "Error: [target] already has ticket #[ticket_id] open"
var/ticket_num
// If the passed in command actually has a ticket id arg
if(split_size >= 3)
ticket_num = text2num(splits[3])
if(isnull(ticket_num))
return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]"
// The active ticket we're trying to reopen, if one exists
var/datum/admin_help/active_ticket = GLOB.ahelp_tickets.TicketByID(ticket_num)
// The ckey of the player to be targeted BY the ticket
// Not the initiator all the time
var/boinked_ckey = active_ticket?.initiator_ckey
if(!active_ticket)
return "Error: Ticket #[ticket_num] not found"
if(boinked_ckey != target)
return "Error: Ticket #[ticket_num] belongs to [boinked_ckey]"
active_ticket.Reopen()
return "Ticket #[ticket_num] successfully reopened"
if("list")
var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target)
var/tickets_length = length(tickets)
if(!tickets_length)
return "None"
var/list/printable_tickets = list()
for(var/datum/admin_help/iterated_ticket in tickets)
// The id of the iterated adminhelp
var/iterated_id = iterated_ticket?.id
var/text = ""
if(iterated_ticket == ticket)
text += "Active: "
text += "#[iterated_id]"
printable_tickets += text
return printable_tickets.Join(", ")
else
return TGS_AHELP_USAGE
return "Error: Ticket could not be found"
// Now that we've handled command processing, we can actually send messages to the client
if(!recipient)
return "Error: No client"
var/adminname
if(CONFIG_GET(flag/show_irc_name))
adminname = tgs_tagged
else
adminname = "Administrator"
var/stealthkey = GetTgsStealthKey()
message = sanitize(copytext_char(message, 1, MAX_MESSAGE_LEN))
message = emoji_parse(message)
if(!message)
return "Error: No message"
// The ckey of our recipient, with a reply link, and their mob if one exists
var/recipient_name_linked = key_name_admin(recipient)
// The ckey of our recipient, with their mob if one exists. No link
var/recipient_name = key_name_admin(recipient)
message_admins("External message from [sender] to [recipient_name_linked] : [message]")
log_admin_private("External PM: [sender] -> [recipient_name] : [message]")
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
html = "<font color='red' size='4'><b>-- Administrator private message --</b></font>",
confidential = TRUE)
recipient.receive_ahelp(
"<a href='?priv_msg=[stealthkey]'>[adminname]</a>",
message,
)
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
html = span_adminsay("<i>Click on the administrator's name to reply.</i>"),
confidential = TRUE)
admin_ticket_log(recipient, "<font color='purple'>PM From [tgs_tagged]: [message]</font>", log_in_blackbox = FALSE)
window_flash(recipient, ignorepref = TRUE)
// Nullcheck because we run a winset in window flash and I do not trust byond
if(recipient)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, 'sound/effects/adminhelp.ogg')
recipient.externalreplyamount = EXTERNALREPLYCOUNT
return "Message Successful"
/// Gets TGS's stealth key, generates one if none is found
/proc/GetTgsStealthKey()
var/static/tgsStealthKey
if(tgsStealthKey)
return tgsStealthKey
tgsStealthKey = generateStealthCkey()
GLOB.stealthminID[EXTERNAL_PM_USER] = tgsStealthKey
return tgsStealthKey
/// Takes an argument which could be either a ckey, /client, or IRC marker, and returns a client if possible
/// Returns [EXTERNAL_PM_USER] if an IRC marker is detected
/// Otherwise returns null
/proc/disambiguate_client(whom)
if(istype(whom, /client))
return whom
if(!istext(whom) || !(length(whom) >= 1))
return null
var/searching_ckey = whom
if(whom[1] == "@")
searching_ckey = findTrueKey(whom)
if(searching_ckey == EXTERNAL_PM_USER)
return EXTERNAL_PM_USER
return GLOB.directory[searching_ckey]
/client/proc/receive_ahelp(reply_to, message, span_class = "adminsay")
to_chat(
src,
type = MESSAGE_TYPE_ADMINPM,
html = "<span class='[span_class]'>Admin PM from-<b>[reply_to]</b>: [message]</span>",
confidential = TRUE,
)
current_ticket?.player_replied = FALSE
SEND_SIGNAL(src, COMSIG_ADMIN_HELP_RECEIVED, message)
#undef EXTERNAL_PM_USER
#undef EXTERNALREPLYCOUNT
#undef TGS_AHELP_USAGE