Files
Aurora.3/code/modules/admin/ticket.dm
Erki 18baa01d0a Ticket improvements (#4432)
Adds some features and fixes some shit:

Closing of tickets sent to Discord will now alert discord as well.
Adds a reminder period for tickets which starts after they're opened. It's intended to remind staff to close their tickets. Say, something like 10 minutes since opening sounds like a solid idea. If the staff who claimed it is offline, then all staff are reminded of it.
Adds the logging of all tickets to the database at the end of round. Sortable by staff closing, person opening, time opened, time closed, and round ID. This is primarily for Aboshehab.
2018-03-25 16:32:12 +03:00

295 lines
10 KiB
Plaintext

var/global/list/tickets = list()
var/global/list/ticket_panels = list()
/datum/ticket
var/owner
var/list/assigned_admins = list()
var/status = TICKET_OPEN
var/list/msgs = list()
var/closed_by
var/id
var/opened_time
// Real time references for SQL based logging.
var/opened_rt
var/closed_rt
var/response_time
var/reminder_timer
/datum/ticket/New(var/owner)
src.owner = owner
tickets |= src
id = tickets.len
opened_time = world.time
opened_rt = world.realtime
if (config.ticket_reminder_period)
reminder_timer = addtimer(CALLBACK(src, .proc/remind), config.ticket_reminder_period SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
/datum/ticket/proc/close(var/client/closed_by)
if(!closed_by)
return
if(status == TICKET_CLOSED)
return
if(status == TICKET_ASSIGNED && !((closed_by.ckey in assigned_admins) || owner == closed_by.ckey) && alert(closed_by, "You are not assigned to this ticket. Are you sure you want to close it?", "Close ticket?" , "Yes" , "No") != "Yes")
return
if(status == TICKET_ASSIGNED && !closed_by.holder) // non-admins can only close a ticket if no admin has taken it
return
var/client/owner_client = client_by_ckey(owner)
if(owner_client && owner_client.adminhelped == ADMINHELPED_DISCORD)
discord_bot.send_to_admins("[key_name(owner_client)]'s request for help has been closed/deemed unnecessary by [key_name(closed_by)].")
owner_client.adminhelped = ADMINHELPED
src.status = TICKET_CLOSED
src.closed_by = closed_by.ckey
src.closed_rt = world.realtime
to_chat(client_by_ckey(src.owner), "<span class='notice'><b>Your ticket has been closed by [closed_by].</b></span>")
message_admins("<span class='notice'><b>[src.owner]</b>'s ticket has been closed by <b>[key_name(closed_by)]</b>.</span>")
update_ticket_panels()
if (reminder_timer)
deltimer(reminder_timer)
return 1
/datum/ticket/proc/take(var/client/assigned_admin)
if(!assigned_admin)
return
if(status == TICKET_CLOSED)
return
if(assigned_admin.ckey == owner)
return
if(status == TICKET_ASSIGNED && ((assigned_admin.ckey in assigned_admins) || alert(assigned_admin, "This ticket is already assigned. Do you want to add yourself to the ticket?", "Join ticket?" , "Yes" , "No") != "Yes"))
return
assigned_admins |= assigned_admin.ckey
src.status = TICKET_ASSIGNED
var/client/owner_client = client_by_ckey(src.owner)
if(owner_client && owner_client.adminhelped == ADMINHELPED_DISCORD)
discord_bot.send_to_admins("[key_name(owner_client)]'s request for help has been taken by [key_name(assigned_admin)].")
owner_client.adminhelped = ADMINHELPED
message_admins("<span class='danger'><b>[key_name(assigned_admin)]</b> has assigned themself to <b>[src.owner]'s</b> ticket.</span>")
to_chat(owner_client, "<span class='notice'><b>[assigned_admin] has added themself to your ticket and should respond shortly. Thanks for your patience!</b></span>")
to_chat(assigned_admin, get_options_bar(owner_client, 2, 1, 1))
update_ticket_panels()
return 1
/datum/ticket/proc/remind()
if (status == TICKET_CLOSED)
reminder_timer = null
return
var/admin_found = FALSE
for (var/ckey in assigned_admins)
var/client/C = client_by_ckey(ckey)
if (C)
admin_found = TRUE
to_chat(C, "<span class='danger'><b>You have yet to close [owner]'s ticket!</b></span>")
sound_to(C, 'sound/effects/adminhelp.ogg')
if (!admin_found)
message_admins("<span class='danger'><b>[owner]'s ticket has yet to be closed!</b></span>")
for(var/client/C in admins)
if((C.holder.rights & (R_ADMIN|R_MOD)) && (C.prefs.toggles & SOUND_ADMINHELP))
sound_to(C, 'sound/effects/adminhelp.ogg')
reminder_timer = addtimer(CALLBACK(src, .proc/remind), config.ticket_reminder_period SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
/proc/get_open_ticket_by_ckey(var/owner)
for(var/datum/ticket/ticket in tickets)
if(ticket.owner == owner && (ticket.status == TICKET_OPEN || ticket.status == TICKET_ASSIGNED))
return ticket // there should only be one open ticket by a client at a time, so no need to keep looking
/datum/ticket/proc/is_active()
if(status != TICKET_ASSIGNED)
return 0
for(var/admin in assigned_admins)
var/client/admin_client = client_by_ckey(admin)
if(admin_client && !admin_client.is_afk())
return 1
return 0
/datum/ticket/proc/log_to_db()
if (status != TICKET_CLOSED)
return
if (!establish_db_connection(dbcon))
return
var/DBQuery/Q = dbcon.NewQuery("INSERT INTO ss13_tickets (game_id, message_count, admin_count, admin_list, opened_by, taken_by, closed_by, response_delay, opened_at, closed_at) VALUES (:g_id:, :m_count:, :a_count:, :a_list:, :opened_by:, :taken_by, :closed_by:, :delay:, :opened_at:, :closed_at:)")
Q.Execute(list("g_id" = game_id, "m_count" = length(msgs), "a_count" = length(assigned_admins), "a_list" = json_encode(assigned_admins), "opened_by" = owner, "taken_by" = assigned_admins[0], "closed_by" = closed_by, "delay" = response_time, "opened_at" = SQLtime(opened_rt), "closed_at" = SQLtime(closed_rt)))
/datum/ticket/proc/append_message(m_from, m_to, msg)
msgs += new /datum/ticket_msg(m_from, m_to, msg)
if (!response_time && m_from != owner)
response_time = round((world.time - opened_time) SECONDS)
update_ticket_panels()
// Referenced in the statistics controller.
/proc/log_all_tickets()
for (var/t in tickets)
var/datum/ticket/T = t
T.log_to_db()
/datum/ticket_msg
var/msg_from
var/msg_to
var/msg
var/time_stamp
/datum/ticket_msg/New(var/msg_from, var/msg_to, var/msg)
src.msg_from = msg_from
src.msg_to = msg_to
src.msg = msg
src.time_stamp = time_stamp()
/datum/ticket_panel
var/datum/ticket/open_ticket = null
var/datum/browser/ticket_panel_window
/datum/ticket_panel/proc/get_dat()
var/client/C = ticket_panel_window.user.client
if(!C)
return
var/list/dat = list()
var/valid_holder = check_rights(R_MOD|R_ADMIN, FALSE, ticket_panel_window.user)
var/list/ticket_dat = list()
for(var/id = tickets.len, id >= 1, id--)
var/datum/ticket/ticket = tickets[id]
if(valid_holder || ticket.owner == C.ckey)
var/client/owner_client = client_by_ckey(ticket.owner)
var/status = "Unknown status"
var/color = "#6aa84f"
switch(ticket.status)
if(TICKET_OPEN)
status = "Opened [round((world.time - ticket.opened_time) / (1 MINUTE))] minute\s ago, unassigned"
if(TICKET_ASSIGNED)
status = "Assigned to [english_list(ticket.assigned_admins, "no one")]"
color = "#ffffff"
if(TICKET_CLOSED)
status = "Closed by [ticket.closed_by]"
color = "#cc2222"
ticket_dat += "<li style='padding-bottom:10px;color:[color]'>"
if(open_ticket && open_ticket == ticket)
ticket_dat += "<i>"
ticket_dat += "Ticket #[id] - [ticket.owner] [owner_client ? "" : "(DC)"] - [status]<br /><a href='byond://?src=\ref[src];action=view;ticket=\ref[ticket]'>VIEW</a>"
if(ticket.status)
ticket_dat += " - <a href='byond://?src=\ref[src];action=pm;ticket=\ref[ticket]'>PM</a>"
if(valid_holder)
ticket_dat += " - <a href='byond://?src=\ref[src];action=take;ticket=\ref[ticket]'>[(ticket.status == TICKET_OPEN) ? "TAKE" : "JOIN"]</a>"
if(ticket.status != TICKET_CLOSED && (valid_holder || ticket.status == TICKET_OPEN))
ticket_dat += " - <a href='byond://?src=\ref[src];action=close;ticket=\ref[ticket]'>CLOSE</a>"
if(valid_holder)
var/ref_mob = ""
if(owner_client)
ref_mob = "\ref[owner_client.mob]"
ticket_dat += " - <A HREF='?_src_=holder;adminmoreinfo=[ref_mob]'>?</A> - <A HREF='?_src_=holder;adminplayeropts=[ref_mob]'>PP</A> - <A HREF='?_src_=vars;Vars=[ref_mob]'>VV</A> - <A HREF='?_src_=holder;subtlemessage=[ref_mob]'>SM</A>[owner_client ? "- [admin_jump_link(owner_client.mob, src)]" : ""]"
if(open_ticket && open_ticket == ticket)
ticket_dat += "</i>"
ticket_dat += "</li>"
if(ticket_dat.len)
dat += "<br /><div style='width:50%;float:left;'><p><b>Available tickets:</b></p><ul>[jointext(ticket_dat, null)]</ul></div>"
if(open_ticket)
dat += "<div style='width:50%;float:left;'><p><b>\[<a href='byond://?src=\ref[src];action=unview;'>X</a>\] Messages for ticket #[open_ticket.id]:</b></p>"
var/list/msg_dat = list()
for(var/datum/ticket_msg/msg in open_ticket.msgs)
var/msg_to = msg.msg_to ? msg.msg_to : "Adminhelp"
msg_dat += "<li>\[[msg.time_stamp]\] [msg.msg_from] -> [msg_to]: [C.holder ? generate_ahelp_key_words(C.mob, msg.msg) : msg.msg]</li>"
if(msg_dat.len)
dat += "<ul>[jointext(msg_dat, null)]</ul></div>"
else
dat += "<p>No messages to display.</p></div>"
else
dat += "<p>No tickets to display.</p>"
dat += "</body></html>"
return jointext(dat, null)
/datum/ticket_panel/Topic(href, href_list)
..()
if(href_list["close"])
ticket_panels -= usr.client
switch(href_list["action"])
if("unview")
open_ticket = null
ticket_panel_window.set_content(get_dat())
ticket_panel_window.update()
var/datum/ticket/ticket = locate(href_list["ticket"])
if(!istype(ticket))
return
switch(href_list["action"])
if("view")
open_ticket = ticket
ticket_panel_window.set_content(get_dat())
ticket_panel_window.update()
if("take")
ticket.take(usr.client)
if("close")
ticket.close(usr.client)
if("pm")
if(check_rights(R_MOD|R_ADMIN) && ticket.owner != usr.ckey)
usr.client.cmd_admin_pm(client_by_ckey(ticket.owner), ticket = ticket)
else if(ticket.status == TICKET_ASSIGNED)
// manually check that the target client exists here as to not spam the usr for each logged out admin on the ticket
var/admin_found = 0
for(var/admin in ticket.assigned_admins)
var/client/admin_client = client_by_ckey(admin)
if(admin_client)
admin_found = 1
usr.client.cmd_admin_pm(admin_client, ticket = ticket)
break
if(!admin_found)
to_chat(usr, "<span class='warning'>Error: Private-Message: Client not found. They may have lost connection, so please be patient!</span>")
else
usr.client.adminhelp(input(usr,"", "adminhelp \"text\"") as text)
/client/verb/view_tickets()
set name = "View Tickets"
set category = "Admin"
var/datum/ticket_panel/ticket_panel = new()
ticket_panels[src] = ticket_panel
ticket_panel.ticket_panel_window = new(src.mob, "ticketpanel", "Ticket Manager", 1024, 768, ticket_panel)
ticket_panel.ticket_panel_window.set_content(ticket_panel.get_dat())
ticket_panel.ticket_panel_window.open()
/proc/update_ticket_panels()
for(var/client/C in ticket_panels)
var/datum/ticket_panel/ticket_panel = ticket_panels[C]
if(C.mob != ticket_panel.ticket_panel_window.user)
C.view_tickets()
else
ticket_panel.ticket_panel_window.set_content(ticket_panel.get_dat())
ticket_panel.ticket_panel_window.update()