mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-01 04:21:42 +00:00
* TGS3 * Fix line endings * Enable 16-bit long topics * Cleans up topic socket usage * Reduces and refines the IRC status throttle * Increase the CP status delay to a reasonable amount * Fixes the testmerge command not having a keyword * Clean up rebooting a bit * Get error codes from windows when symlinking fails * Clean up world announces * Aborting compilation will kill the DD process * Add support for changing the project name * Removes the log page * Add support for compile cancellation * Version bumps and docs * Add merge-pr repo CL command * Fixes DM cancel command's help message * Refactor command line to show better formatted help text * Corrects a typo * Multi-key game options must be manually edited * Moving of the server installation from the control panel * Fix a bug with server moving * Corrects webclient disposal syntax * Service now handles the PR Json the game uses properly * PR listing command for CL * Fixes reversed testmerge and update help entries * Windows scheduling to help avoid reboot crashes * Generalization of chat infastructure * Brings a file name in line with everything else * Shutdown exceptions no longer keep the service online * Enable provider switching on the backend. More thread safety * Support for switching, password encryption and defaults. * Removes boilerplate on log writing * Discord integration * Update the installer dependencies * Version bump * Adds support for getting the latest byond version * Fixes issue with not being able to set discord channels * Fix being able to reconnect if chat is disabled * Extra validation for interface types * Add the Chat page for the control panel * Various cleanup * Set read ACL on the data directory * Remove redundant namespace usage * Fixes some buttons not updating the server page * Future proof against upcoming removal of repo data directory * Normalize Main declaration * Update the IRC library * Enables CTCP * Removes useless hack * Logging + enable IRC private messages * Jobs config * And finally the maps config * Save the last config panel visited * Not gonna use these * Minor formatting cleanup * Fixes the chat page not refereshing after clicking reconnect * Fixes server page not initializing correctly * Repo now defaults to tgstation github when not found * Revert "Set read ACL on the data directory" This reverts commit 15b0021ec51532bca14690a884caa81e811fbc46. * Design the admin config page * Prep format the repo's admin_ranks.txt * Add a negative permissions field * IRC now RFC quits before disconnecting * Turns out that fixed the disconnect lag * Updates the admin ranks config api to work for us * Done with this config shit * @optimumtact * Fix this * Fix the .wxs * Try to get md5/sha1 working. * Add FCIV to appveyor * Generalize the command class * Revert "Generalize the command class" This reverts commit 5c61f6df58d66f0fea4170c8aee0cd5beaa99b5d. * ITS THE FUCKING SEX NUMBER!!!! * Final touches * No THESE are the final touches * Do not advertise * Revert "Do not advertise" This reverts commit f64281d486f9ca27e39f19635ab4deacb2d7e1ac. * Hopefully the last version bump for long time * Fix line endings * Fix default dbconfig.txt * Fix Discord not checking the right admin channel * Fix discord listening on ALL channels instead of configured ones * Package the discord fixes for @JamieH * Format the testmerge data a little better * Apply 7 character clamping of commit strings * Fold admin hard reboot into regular reboot list * Backward ahelp compatibility with the adminbus bot * Removes an unecessary semicolon * Fix stray merge conflict in the config * Fix Newtonsoft being included by the commandline * Improve byond update logging * Chat cleanup * Fixes some setup non-errors from being displayed * Repository no longer counts being busy as being valid * Repository no longer valid while cloning * Fixes a nudge socket change issue * Frontend cleanup * Fixes CanStart race condition * Fixes compile cancel delays * Various fixes * More fixes * Better readme * More readme * Fix a config command description * Add missing repo status command * Never delete the backups * Log the compiles * More logging * Stuff * A thing happened, but I'm not sure what * Tiny * INB4 second squash * Version bump * Shallow clones should speed things up * Regular clones * This is how it's set on travis * Fix this dupe * Add backup tag support to backend and command line * Add some missing repo commands, fix GetHead. Fix reset on branches * Remove the interfaces for commit and push * Remove that generate changelog checkbox * Yeah, that's a misunderstanding * Make changelog pushing a config, with no way to enable for now * Add Reset and Recompile option * Update readme * Repo page cleanup * Fixed NudgePort message possible repeating Fixed Reset and Recompile option always being visible * Fixed compilation copy not overwriting files Fixed compiler trying to unecessarily delete the whole A/B folder Improved game folder initialization speed * Selectively stage the html folder * Make the restriction a config * Switch to using LibGit2Sharp+SSH * WIP SSH support * Removes some success chat messages * Make repo authentication purely file based * Quick IRC fix * Should all work in theory... * More fine grained * Remove the username thing * Use the right default email for tgstation-server * Update the readme * That's worthy of a version bump * Speling * Makes it do as the readme says * Fix testmerge list not having a scrollbar * Trying out commit message based deployment [TGSDeploy] * Whoops * Testing * Better * Version Bump [TGSDeploy] * Need to set the var at parent scope [TGSDeploy] * Use the commit message * Try this [TGSDeploy] * Try just this * This maybe? [TGSDeploy] * >like [TGSDeploy] * Wildcard, bitches [TGSDeploy] * Saner title [TGSDeploy] * Readme update * This should loin ya * Fix it [TGSDeploy] * Readme, cleanup, and doc updates * Improve DD crash handling * Version bump [TGSDeploy] * TGS3 Config Changes * Line endings * Map config code change * Missed a few * Security and Visibility selectors for the Server page * Fixes OCD * Fax it * Fixes * Version bump [TGSDeploy] * eh * The word comment has lost it's meaning to me * This is a terrible name but whatever * Support config changes * This is part of the code so it belongs with the code * ExportService now has a return value * Copying of the logs dir during compile for #27674 * Version bump [TGSDeploy] * Removes some uneedful * Moves daemon config to BYOND folder, much safer * Fix a config comment translation miss * Fix project settings issue * Fix config apply button not showing up after repo clone * Fix anchoring for Backup Tags: label * Version Bump [TGSDeploy] * Nudge port only listens while server is running * Fix some instances of the control panel crashing when the service stops * Add start menu shortcuts * Remove the actual server * Remove appveyor * Fix gitignore * And this * Readd HTTPS_Get for now * Readd legacy support * Fix * Fix this stuff * Last thing * Line endings * Final touches * Dat newline * More stuff * Where'd that go? * Real final touches
672 lines
21 KiB
Plaintext
672 lines
21 KiB
Plaintext
/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb
|
|
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
|
|
|
//
|
|
//TICKET MANAGER
|
|
//
|
|
|
|
GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
|
|
|
/datum/admin_help_tickets
|
|
var/list/active_tickets = list()
|
|
var/list/closed_tickets = list()
|
|
var/list/resolved_tickets = list()
|
|
|
|
var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE)
|
|
var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED)
|
|
var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
|
|
|
|
/datum/admin_help_tickets/Destroy()
|
|
QDEL_LIST(active_tickets)
|
|
QDEL_LIST(closed_tickets)
|
|
QDEL_LIST(resolved_tickets)
|
|
QDEL_NULL(astatclick)
|
|
QDEL_NULL(cstatclick)
|
|
QDEL_NULL(rstatclick)
|
|
return ..()
|
|
|
|
//private
|
|
/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket)
|
|
var/list/ticket_list
|
|
switch(new_ticket.state)
|
|
if(AHELP_ACTIVE)
|
|
ticket_list = active_tickets
|
|
if(AHELP_CLOSED)
|
|
ticket_list = closed_tickets
|
|
if(AHELP_RESOLVED)
|
|
ticket_list = resolved_tickets
|
|
else
|
|
CRASH("Invalid ticket state: [new_ticket.state]")
|
|
var/num_closed = ticket_list.len
|
|
if(num_closed)
|
|
for(var/I in 1 to num_closed)
|
|
var/datum/admin_help/AH = ticket_list[I]
|
|
if(AH.id > new_ticket.id)
|
|
ticket_list.Insert(I, new_ticket)
|
|
return
|
|
ticket_list += new_ticket
|
|
|
|
//opens the ticket listings for one of the 3 states
|
|
/datum/admin_help_tickets/proc/BrowseTickets(state)
|
|
var/list/l2b
|
|
var/title
|
|
switch(state)
|
|
if(AHELP_ACTIVE)
|
|
l2b = active_tickets
|
|
title = "Active Tickets"
|
|
if(AHELP_CLOSED)
|
|
l2b = closed_tickets
|
|
title = "Closed Tickets"
|
|
if(AHELP_RESOLVED)
|
|
l2b = resolved_tickets
|
|
title = "Resolved Tickets"
|
|
if(!l2b)
|
|
return
|
|
var/list/dat = list("<html><head><title>[title]</title></head>")
|
|
dat += "<A HREF='?_src_=holder;ahelp_tickets=[state]'>Refresh</A><br><br>"
|
|
for(var/I in l2b)
|
|
var/datum/admin_help/AH = I
|
|
dat += "<span class='adminnotice'><span class='adminhelp'>Ticket #[AH.id]</span>: <A HREF='?_src_=holder;ahelp=\ref[AH];ahelp_action=ticket'>[AH.initiator_key_name]: [AH.name]</A></span><br>"
|
|
|
|
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
|
|
|
|
//Tickets statpanel
|
|
/datum/admin_help_tickets/proc/stat_entry()
|
|
var/num_disconnected = 0
|
|
stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
|
|
for(var/I in active_tickets)
|
|
var/datum/admin_help/AH = I
|
|
if(AH.initiator)
|
|
stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update())
|
|
else
|
|
++num_disconnected
|
|
if(num_disconnected)
|
|
stat("Disconnected:", astatclick.update("[num_disconnected]"))
|
|
stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]"))
|
|
stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
|
|
|
|
//Reassociate still open ticket if one exists
|
|
/datum/admin_help_tickets/proc/ClientLogin(client/C)
|
|
C.current_ticket = CKey2ActiveTicket(C.ckey)
|
|
if(C.current_ticket)
|
|
C.current_ticket.AddInteraction("Client reconnected.")
|
|
C.current_ticket.initiator = C
|
|
|
|
//Dissasociate ticket
|
|
/datum/admin_help_tickets/proc/ClientLogout(client/C)
|
|
if(C.current_ticket)
|
|
C.current_ticket.AddInteraction("Client disconnected.")
|
|
C.current_ticket.initiator = null
|
|
C.current_ticket = null
|
|
|
|
//Get a ticket given a ckey
|
|
/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey)
|
|
for(var/I in active_tickets)
|
|
var/datum/admin_help/AH = I
|
|
if(AH.initiator_ckey == ckey)
|
|
return AH
|
|
|
|
//
|
|
//TICKET LIST STATCLICK
|
|
//
|
|
|
|
/obj/effect/statclick/ticket_list
|
|
var/current_state
|
|
|
|
/obj/effect/statclick/ticket_list/New(loc, name, state)
|
|
current_state = state
|
|
..()
|
|
|
|
/obj/effect/statclick/ticket_list/Click()
|
|
GLOB.ahelp_tickets.BrowseTickets(current_state)
|
|
|
|
//
|
|
//TICKET DATUM
|
|
//
|
|
|
|
/datum/admin_help
|
|
var/id
|
|
var/name
|
|
var/state = AHELP_ACTIVE
|
|
|
|
var/opened_at
|
|
var/closed_at
|
|
|
|
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
|
var/initiator_ckey
|
|
var/initiator_key_name
|
|
|
|
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
|
|
|
var/obj/effect/statclick/ahelp/statclick
|
|
|
|
var/static/ticket_counter = 0
|
|
|
|
//call this on its own to create a ticket, don't manually assign current_ticket
|
|
//msg is the title of the ticket: usually the ahelp text
|
|
//is_bwoink is TRUE if this ticket was started by an admin PM
|
|
/datum/admin_help/New(msg, client/C, is_bwoink)
|
|
//clean the input msg
|
|
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
|
if(!msg || !C || !C.mob)
|
|
qdel(src)
|
|
return
|
|
|
|
id = ++ticket_counter
|
|
opened_at = world.time
|
|
|
|
name = msg
|
|
|
|
initiator = C
|
|
initiator_ckey = initiator.ckey
|
|
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
|
if(initiator.current_ticket) //This is a bug
|
|
stack_trace("Multiple ahelp current_tickets")
|
|
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
|
|
initiator.current_ticket.Close()
|
|
initiator.current_ticket = src
|
|
|
|
TimeoutVerb()
|
|
|
|
var/parsed_message = keywords_lookup(msg)
|
|
|
|
statclick = new(null, src)
|
|
_interactions = list()
|
|
|
|
if(is_bwoink)
|
|
AddInteraction("<font color='blue'>[key_name_admin(usr)] PM'd [LinkedReplyName()]</font>")
|
|
message_admins("<font color='blue'>Ticket [TicketHref("#[id]")] created</font>")
|
|
else
|
|
MessageNoRecipient(parsed_message)
|
|
|
|
//show it to the person adminhelping too
|
|
to_chat(C, "<span class='adminnotice'>PM to-<b>Admins</b>: [name]</span>")
|
|
|
|
//send it to irc if nobody is on and tell us how many were on
|
|
var/admin_number_present = send2irc_adminless_only("#[id] [initiator_ckey]", name)
|
|
log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
|
|
if(admin_number_present <= 0)
|
|
to_chat(C, "<span class='notice'>No active admins are online, your adminhelp was sent to the admin irc.</span>")
|
|
|
|
GLOB.ahelp_tickets.active_tickets += src
|
|
|
|
/datum/admin_help/Destroy()
|
|
RemoveActive()
|
|
GLOB.ahelp_tickets.closed_tickets -= src
|
|
GLOB.ahelp_tickets.resolved_tickets -= src
|
|
return ..()
|
|
|
|
/datum/admin_help/proc/AddInteraction(formatted_message)
|
|
_interactions += "[gameTimestamp()]: [formatted_message]"
|
|
|
|
//Removes the ahelp verb and returns it after 2 minutes
|
|
/datum/admin_help/proc/TimeoutVerb()
|
|
initiator.verbs -= /client/verb/adminhelp
|
|
initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps
|
|
|
|
//private
|
|
/datum/admin_help/proc/FullMonty(ref_src)
|
|
if(!ref_src)
|
|
ref_src = "\ref[src]"
|
|
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
|
if(state == AHELP_ACTIVE)
|
|
. += ClosureLinks(ref_src)
|
|
|
|
//private
|
|
/datum/admin_help/proc/ClosureLinks(ref_src)
|
|
if(!ref_src)
|
|
ref_src = "\ref[src]"
|
|
. = " (<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=reject'>REJT</A>)"
|
|
. += " (<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=icissue'>IC</A>)"
|
|
. += " (<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=close'>CLOSE</A>)"
|
|
. += " (<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=resolve'>RSLVE</A>)"
|
|
|
|
//private
|
|
/datum/admin_help/proc/LinkedReplyName(ref_src)
|
|
if(!ref_src)
|
|
ref_src = "\ref[src]"
|
|
return "<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=reply'>[initiator_key_name]</A>"
|
|
|
|
//private
|
|
/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
|
if(!ref_src)
|
|
ref_src = "\ref[src]"
|
|
return "<A HREF='?_src_=holder;ahelp=[ref_src];ahelp_action=[action]'>[msg]</A>"
|
|
|
|
//message from the initiator without a target, all admins will see this
|
|
//won't bug irc
|
|
/datum/admin_help/proc/MessageNoRecipient(msg)
|
|
var/ref_src = "\ref[src]"
|
|
var/chat_msg = "<span class='adminnotice'><span class='adminhelp'>Ticket [TicketHref("#[id]", ref_src)]</span><b>: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:</b> [msg]</span>"
|
|
|
|
AddInteraction("<font color='red'>[LinkedReplyName(ref_src)]: [msg]</font>")
|
|
//send this msg to all admins
|
|
|
|
for(var/client/X in GLOB.admins)
|
|
if(X.prefs.toggles & SOUND_ADMINHELP)
|
|
X << 'sound/effects/adminhelp.ogg'
|
|
window_flash(X, ignorepref = TRUE)
|
|
to_chat(X, chat_msg)
|
|
|
|
//Reopen a closed ticket
|
|
/datum/admin_help/proc/Reopen()
|
|
if(state == AHELP_ACTIVE)
|
|
to_chat(usr, "<span class='warning'>This ticket is already open.</span>")
|
|
return
|
|
|
|
if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
|
to_chat(usr, "<span class='warning'>This user already has an active ticket, cannot reopen this one.</span>")
|
|
return
|
|
|
|
statclick = new(null, src)
|
|
GLOB.ahelp_tickets.active_tickets += src
|
|
GLOB.ahelp_tickets.closed_tickets -= src
|
|
GLOB.ahelp_tickets.resolved_tickets -= src
|
|
switch(state)
|
|
if(AHELP_CLOSED)
|
|
SSblackbox.dec("ahelp_close")
|
|
if(AHELP_RESOLVED)
|
|
SSblackbox.dec("ahelp_resolve")
|
|
state = AHELP_ACTIVE
|
|
closed_at = null
|
|
if(initiator)
|
|
initiator.current_ticket = src
|
|
|
|
AddInteraction("<font color='purple'>Reopened by [key_name_admin(usr)]</font>")
|
|
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].</span>"
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
SSblackbox.inc("ahelp_reopen")
|
|
TicketPanel() //can only be done from here, so refresh it
|
|
|
|
//private
|
|
/datum/admin_help/proc/RemoveActive()
|
|
if(state != AHELP_ACTIVE)
|
|
return
|
|
closed_at = world.time
|
|
QDEL_NULL(statclick)
|
|
GLOB.ahelp_tickets.active_tickets -= src
|
|
if(initiator && initiator.current_ticket == src)
|
|
initiator.current_ticket = null
|
|
|
|
//Mark open ticket as closed/meme
|
|
/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE)
|
|
if(state != AHELP_ACTIVE)
|
|
return
|
|
RemoveActive()
|
|
state = AHELP_CLOSED
|
|
GLOB.ahelp_tickets.ListInsert(src)
|
|
AddInteraction("<font color='red'>Closed by [key_name].</font>")
|
|
if(!silent)
|
|
SSblackbox.inc("ahelp_close")
|
|
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]."
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
|
|
//Mark open ticket as resolved/legitimate, returns ahelp verb
|
|
/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE)
|
|
if(state != AHELP_ACTIVE)
|
|
return
|
|
RemoveActive()
|
|
state = AHELP_RESOLVED
|
|
GLOB.ahelp_tickets.ListInsert(src)
|
|
|
|
if(initiator)
|
|
initiator.giveadminhelpverb()
|
|
|
|
AddInteraction("<font color='green'>Resolved by [key_name].</font>")
|
|
if(!silent)
|
|
SSblackbox.inc("ahelp_resolve")
|
|
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]"
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
|
|
//Close and return ahelp verb, use if ticket is incoherent
|
|
/datum/admin_help/proc/Reject(key_name = key_name_admin(usr))
|
|
if(state != AHELP_ACTIVE)
|
|
return
|
|
|
|
if(initiator)
|
|
initiator.giveadminhelpverb()
|
|
|
|
initiator << 'sound/effects/adminhelp.ogg'
|
|
|
|
to_chat(initiator, "<font color='red' size='4'><b>- AdminHelp Rejected! -</b></font>")
|
|
to_chat(initiator, "<font color='red'><b>Your admin help was rejected.</b> The adminhelp verb has been returned to you so that you may try again.</font>")
|
|
to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.")
|
|
|
|
SSblackbox.inc("ahelp_reject")
|
|
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]"
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
AddInteraction("Rejected by [key_name].")
|
|
Close(silent = TRUE)
|
|
|
|
//Resolve ticket with IC Issue message
|
|
/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr))
|
|
if(state != AHELP_ACTIVE)
|
|
return
|
|
|
|
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as IC issue! -</b></font><br>"
|
|
msg += "<font color='red'><b>Losing is part of the game!</b></font><br>"
|
|
msg += "<font color='red'>Your character will frequently die, sometimes without even a possibility of avoiding it. Events will often be out of your control. No matter how good or prepared you are, sometimes you just lose.</font>"
|
|
|
|
if(initiator)
|
|
to_chat(initiator, msg)
|
|
|
|
SSblackbox.inc("ahelp_icissue")
|
|
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]"
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
AddInteraction("Marked as IC issue by [key_name]")
|
|
Resolve(silent = TRUE)
|
|
|
|
//Show the ticket panel
|
|
/datum/admin_help/proc/TicketPanel()
|
|
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
|
var/ref_src = "\ref[src]"
|
|
dat += "<h4>Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
|
dat += "<b>State: "
|
|
switch(state)
|
|
if(AHELP_ACTIVE)
|
|
dat += "<font color='red'>OPEN</font>"
|
|
if(AHELP_RESOLVED)
|
|
dat += "<font color='green'>RESOLVED</font>"
|
|
if(AHELP_CLOSED)
|
|
dat += "CLOSED"
|
|
else
|
|
dat += "UNKNOWN"
|
|
dat += "</b>[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]"
|
|
if(state != AHELP_ACTIVE)
|
|
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
|
|
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
|
if(closed_at)
|
|
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
|
dat += "<br><br>"
|
|
if(initiator)
|
|
dat += "<b>Actions:</b> [FullMonty(ref_src)]<br>"
|
|
else
|
|
dat += "<b>DISCONNECTED</b>[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
|
dat += "<br><b>Log:</b><br><br>"
|
|
for(var/I in _interactions)
|
|
dat += "[I]<br>"
|
|
|
|
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
|
|
|
|
/datum/admin_help/proc/Retitle()
|
|
var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null
|
|
if(new_title)
|
|
name = new_title
|
|
//not saying the original name cause it could be a long ass message
|
|
var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]"
|
|
message_admins(msg)
|
|
log_admin_private(msg)
|
|
TicketPanel() //we have to be here to do this
|
|
|
|
//Forwarded action from admin/Topic
|
|
/datum/admin_help/proc/Action(action)
|
|
testing("Ahelp action: [action]")
|
|
switch(action)
|
|
if("ticket")
|
|
TicketPanel()
|
|
if("retitle")
|
|
Retitle()
|
|
if("reject")
|
|
Reject()
|
|
if("reply")
|
|
usr.client.cmd_ahelp_reply(initiator)
|
|
if("icissue")
|
|
ICIssue()
|
|
if("close")
|
|
Close()
|
|
if("resolve")
|
|
Resolve()
|
|
if("reopen")
|
|
Reopen()
|
|
|
|
//
|
|
// TICKET STATCLICK
|
|
//
|
|
|
|
/obj/effect/statclick/ahelp
|
|
var/datum/admin_help/ahelp_datum
|
|
|
|
/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH)
|
|
ahelp_datum = AH
|
|
. = ..()
|
|
|
|
/obj/effect/statclick/ahelp/update()
|
|
return ..(ahelp_datum.name)
|
|
|
|
/obj/effect/statclick/ahelp/Click()
|
|
ahelp_datum.TicketPanel()
|
|
|
|
/obj/effect/statclick/ahelp/Destroy()
|
|
ahelp_datum = null
|
|
return ..()
|
|
|
|
//
|
|
// CLIENT PROCS
|
|
//
|
|
|
|
/client/proc/giveadminhelpverb()
|
|
src.verbs |= /client/verb/adminhelp
|
|
deltimer(adminhelptimerid)
|
|
adminhelptimerid = 0
|
|
|
|
/client/verb/adminhelp(msg as text)
|
|
set category = "Admin"
|
|
set name = "Adminhelp"
|
|
|
|
if(GLOB.say_disabled) //This is here to try to identify lag problems
|
|
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
|
return
|
|
|
|
//handle muting and automuting
|
|
if(prefs.muted & MUTE_ADMINHELP)
|
|
to_chat(src, "<span class='danger'>Error: Admin-PM: You cannot send adminhelps (Muted).</span>")
|
|
return
|
|
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
|
return
|
|
|
|
if(!msg)
|
|
return
|
|
|
|
SSblackbox.add_details("admin_verb","Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
|
if(current_ticket)
|
|
if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No")
|
|
if(current_ticket)
|
|
current_ticket.MessageNoRecipient(msg)
|
|
current_ticket.TimeoutVerb()
|
|
return
|
|
else
|
|
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
|
else
|
|
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
|
current_ticket.Close()
|
|
|
|
new /datum/admin_help(msg, src, FALSE)
|
|
|
|
//admin proc
|
|
/client/proc/cmd_admin_ticket_panel()
|
|
set name = "Show Ticket List"
|
|
set category = "Admin"
|
|
|
|
if(!check_rights(R_ADMIN, TRUE))
|
|
return
|
|
|
|
var/browse_to
|
|
|
|
switch(input("Display which ticket list?") as null|anything in list("Active Tickets", "Closed Tickets", "Resolved Tickets"))
|
|
if("Active Tickets")
|
|
browse_to = AHELP_ACTIVE
|
|
if("Closed Tickets")
|
|
browse_to = AHELP_CLOSED
|
|
if("Resolved Tickets")
|
|
browse_to = AHELP_RESOLVED
|
|
else
|
|
return
|
|
|
|
GLOB.ahelp_tickets.BrowseTickets(browse_to)
|
|
|
|
//
|
|
// LOGGING
|
|
//
|
|
|
|
//Use this proc when an admin takes action that may be related to an open ticket on what
|
|
//what can be a client, ckey, or mob
|
|
/proc/admin_ticket_log(what, message)
|
|
var/client/C
|
|
var/mob/Mob = what
|
|
if(istype(Mob))
|
|
C = Mob.client
|
|
else
|
|
C = what
|
|
if(istype(C) && C.current_ticket)
|
|
C.current_ticket.AddInteraction(message)
|
|
return C.current_ticket
|
|
if(istext(what)) //ckey
|
|
var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what)
|
|
if(AH)
|
|
AH.AddInteraction(message)
|
|
return AH
|
|
|
|
//
|
|
// HELPER PROCS
|
|
//
|
|
|
|
/proc/get_admin_counts(requiredflags = R_BAN)
|
|
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
|
|
for(var/client/X in GLOB.admins)
|
|
.["total"] += X
|
|
if(requiredflags != 0 && !check_rights_for(X, requiredflags))
|
|
.["noflags"] += X
|
|
else if(X.is_afk())
|
|
.["afk"] += X
|
|
else if(X.holder.fakekey)
|
|
.["stealth"] += X
|
|
else
|
|
.["present"] += X
|
|
|
|
/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
|
|
var/list/adm = get_admin_counts(requiredflags)
|
|
var/list/activemins = adm["present"]
|
|
. = activemins.len
|
|
if(. <= 0)
|
|
var/final = ""
|
|
var/list/afkmins = adm["afk"]
|
|
var/list/stealthmins = adm["stealth"]
|
|
var/list/powerlessmins = adm["noflags"]
|
|
var/list/allmins = adm["total"]
|
|
if(!afkmins.len && !stealthmins.len && !powerlessmins.len)
|
|
final = "[msg] - No admins online"
|
|
else
|
|
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
|
|
send2irc(source,final)
|
|
send2otherserver(source,final)
|
|
|
|
|
|
/proc/send2irc(msg,msg2)
|
|
if(world.RunningService())
|
|
world.ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [msg] | [msg2]")
|
|
else if(config.useircbot)
|
|
shell("python nudge.py [msg] [msg2]")
|
|
|
|
/proc/send2otherserver(source,msg,type = "Ahelp")
|
|
if(config.cross_allowed)
|
|
var/list/message = list()
|
|
message["message_sender"] = source
|
|
message["message"] = msg
|
|
message["source"] = "([config.cross_name])"
|
|
message["key"] = global.comms_key
|
|
message["crossmessage"] = type
|
|
|
|
world.Export("[config.cross_address]?[list2params(message)]")
|
|
|
|
|
|
/proc/ircadminwho()
|
|
var/list/message = list("Admins: ")
|
|
var/list/admin_keys = list()
|
|
for(var/adm in GLOB.admins)
|
|
var/client/C = adm
|
|
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
|
|
|
|
for(var/admin in admin_keys)
|
|
if(LAZYLEN(message) > 1)
|
|
message += ", [admin]"
|
|
else
|
|
message += "[admin]"
|
|
|
|
return jointext(message, "")
|
|
|
|
/proc/keywords_lookup(msg,irc)
|
|
|
|
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
|
|
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
|
|
|
|
//explode the input msg into a list
|
|
var/list/msglist = splittext(msg, " ")
|
|
|
|
//generate keywords lookup
|
|
var/list/surnames = list()
|
|
var/list/forenames = list()
|
|
var/list/ckeys = list()
|
|
var/founds = ""
|
|
for(var/mob/M in GLOB.mob_list)
|
|
var/list/indexing = list(M.real_name, M.name)
|
|
if(M.mind)
|
|
indexing += M.mind.name
|
|
|
|
for(var/string in indexing)
|
|
var/list/L = splittext(string, " ")
|
|
var/surname_found = 0
|
|
//surnames
|
|
for(var/i=L.len, i>=1, i--)
|
|
var/word = ckey(L[i])
|
|
if(word)
|
|
surnames[word] = M
|
|
surname_found = i
|
|
break
|
|
//forenames
|
|
for(var/i=1, i<surname_found, i++)
|
|
var/word = ckey(L[i])
|
|
if(word)
|
|
forenames[word] = M
|
|
//ckeys
|
|
ckeys[M.ckey] = M
|
|
|
|
var/ai_found = 0
|
|
msg = ""
|
|
var/list/mobs_found = list()
|
|
for(var/original_word in msglist)
|
|
var/word = ckey(original_word)
|
|
if(word)
|
|
if(!(word in adminhelp_ignored_words))
|
|
if(word == "ai")
|
|
ai_found = 1
|
|
else
|
|
var/mob/found = ckeys[word]
|
|
if(!found)
|
|
found = surnames[word]
|
|
if(!found)
|
|
found = forenames[word]
|
|
if(found)
|
|
if(!(found in mobs_found))
|
|
mobs_found += found
|
|
if(!ai_found && isAI(found))
|
|
ai_found = 1
|
|
var/is_antag = 0
|
|
if(found.mind && found.mind.special_role)
|
|
is_antag = 1
|
|
founds += "Name: [found.name]([found.real_name]) Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
|
|
msg += "[original_word]<font size='1' color='[is_antag ? "red" : "black"]'>(<A HREF='?_src_=holder;adminmoreinfo=\ref[found]'>?</A>|<A HREF='?_src_=holder;adminplayerobservefollow=\ref[found]'>F</A>)</font> "
|
|
continue
|
|
msg += "[original_word] "
|
|
if(irc)
|
|
if(founds == "")
|
|
return "Search Failed"
|
|
else
|
|
return founds
|
|
|
|
return msg
|