var/list/chatResources = list(
"goon/browserassets/js/jquery.min.js",
"goon/browserassets/js/jquery.mark.min.js",
"goon/browserassets/js/json2.min.js",
"goon/browserassets/js/twemoji.min.js",
"goon/browserassets/js/browserOutput.js",
"goon/browserassets/css/fonts/fontawesome-webfont.eot",
"goon/browserassets/css/fonts/fontawesome-webfont.svg",
"goon/browserassets/css/fonts/fontawesome-webfont.ttf",
"goon/browserassets/css/fonts/fontawesome-webfont.woff",
"goon/browserassets/css/fonts/PxPlus_IBM_MDA.ttf",
"goon/browserassets/css/font-awesome.css",
"goon/browserassets/css/browserOutput.css",
"goon/browserassets/css/browserOutput-dark.css",
"goon/browserassets/json/unicode_9_annotations.json",
"goon/browserassets/html/saveInstructions.html"
)
/var/savefile/iconCache = new /savefile("data/iconCache.sav")
/var/chatDebug = file("data/chatDebug.log")
/datum/chatOutput
var/client/owner = null
var/loaded = 0
var/list/messageQueue = list()
var/cookieSent = 0
var/list/connectionHistory = list()
var/broken = FALSE
/datum/chatOutput/New(client/C)
. = ..()
owner = C
/datum/chatOutput/proc/start()
if(!owner)
return 0
if(!winexists(owner, "browseroutput"))
spawn()
alert(owner.mob, "Updated chat window does not exist. If you are using a custom skin file please allow the game to update.")
broken = TRUE
return 0
if(!owner) // In case the client vanishes before winexists returns
return 0
if(winget(owner, "browseroutput", "is-disabled") == "false")
doneLoading()
else
load()
return 1
/datum/chatOutput/proc/load()
set waitfor = FALSE
if(!owner)
return
for(var/attempts in 1 to 5)
for(var/asset in global.chatResources)
owner << browse_rsc(file(asset))
for(var/subattempts in 1 to 3)
owner << browse(file2text("goon/browserassets/html/browserOutput.html"), "window=browseroutput")
sleep(10 SECONDS)
if(!owner || loaded)
return
/datum/chatOutput/Topic(var/href, var/list/href_list)
if(usr.client != owner)
return 1
var/list/params = list()
for(var/key in href_list)
if(length(key) > 7 && findtext(key, "param"))
var/param_name = copytext(key, 7, -1)
var/item = href_list[key]
params[param_name] = item
var/data
switch(href_list["proc"])
if("doneLoading")
data = doneLoading(arglist(params))
if("debug")
data = debug(arglist(params))
if("ping")
data = ping(arglist(params))
if("analyzeClientData")
data = analyzeClientData(arglist(params))
if(data)
ehjax_send(data = data)
/datum/chatOutput/proc/doneLoading()
if(loaded)
return
loaded = TRUE
winset(owner, "browseroutput", "is-disabled=false")
if(owner.holder)
loadAdmin()
for(var/message in messageQueue)
to_chat(owner, message)
messageQueue = null
src.sendClientData()
pingLoop()
/datum/chatOutput/proc/pingLoop()
set waitfor = FALSE
while (owner)
ehjax_send(data = owner.is_afk(29 SECONDS) ? "softPang" : "pang") // SoftPang isn't handled anywhere but it'll always reset the opts.lastPang.
sleep(30 SECONDS)
/datum/chatOutput/proc/ehjax_send(var/client/C = owner, var/window = "browseroutput", var/data)
if(islist(data))
data = json_encode(data)
C << output("[data]", "[window]:ehjaxCallback")
/datum/chatOutput/proc/loadAdmin()
var/data = json_encode(list("loadAdminCode" = replacetext(replacetext(file2text("goon/browserassets/html/adminOutput.html"), "\n", ""), "\t", "")))
ehjax_send(data = url_encode(data))
/datum/chatOutput/proc/sendClientData()
var/list/deets = list("clientData" = list())
deets["clientData"]["ckey"] = owner.ckey
deets["clientData"]["ip"] = owner.address
deets["clientData"]["compid"] = owner.computer_id
var/data = json_encode(deets)
ehjax_send(data = data)
/datum/chatOutput/proc/analyzeClientData(cookie = "")
if(!cookie)
return
if(cookie != "none")
var/list/connData = json_decode(cookie)
if(connData && islist(connData) && connData.len > 0 && connData["connData"])
connectionHistory = connData["connData"]
var/list/found = new()
for(var/i = connectionHistory.len; i >= 1; i--)
var/list/row = connectionHistory[i]
if(!row || row.len < 3 || !(row["ckey"] && row["compid"] && row["ip"]))
return
if(world.IsBanned(row["ckey"], row["compid"], row["ip"], FALSE))
found = row
break
//Uh oh this fucker has a history of playing on a banned account!!
if (found.len > 0)
//TODO: add a new evasion ban for the CURRENT client details, using the matched row details
message_admins("[key_name(src.owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
log_admin("[key_name(src.owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
cookieSent = 1
/datum/chatOutput/proc/ping()
return "pong"
/datum/chatOutput/proc/debug(error)
error = "\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client : [owner.key ? owner.key : owner] triggered JS error: [error]"
chatDebug << error
/client/verb/debug_chat()
set hidden = 1
chatOutput.ehjax_send(data = list("firebug" = 1))
/var/list/bicon_cache = list()
//Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
// exporting it as text, and then parsing the base64 from that.
// (This relies on byond automatically storing icons in savefiles as base64)
/proc/icon2base64(var/icon/icon, var/iconKey = "misc")
if (!isicon(icon)) return 0
iconCache[iconKey] << icon
var/iconData = iconCache.ExportText(iconKey)
var/list/partial = splittext(iconData, "{")
return replacetext(copytext(partial[2], 3, -5), "\n", "")
/proc/bicon(var/obj, var/use_class = 1)
var/class = use_class ? "class='icon misc'" : null
if (!obj)
return
if (isicon(obj))
if (!bicon_cache["\ref[obj]"]) // Doesn't exist yet, make it.
bicon_cache["\ref[obj]"] = icon2base64(obj)
return "
"
// Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with.
var/atom/A = obj
var/key = "[istype(A.icon, /icon) ? "\ref[A.icon]" : A.icon]:[A.icon_state]"
if (!bicon_cache[key]) // Doesn't exist, make it.
var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1)
if (ishuman(obj)) // Shitty workaround for a BYOND issue.
var/icon/temp = I
I = icon()
I.Insert(temp, dir = SOUTH)
bicon_cache[key] = icon2base64(I, key)
if(use_class)
class = "class='icon [A.icon_state]'"
return "
"
/proc/is_valid_tochat_message(message)
return istext(message)
/proc/is_valid_tochat_target(target)
return !istype(target, /savefile) && (ismob(target) || islist(target) || isclient(target) || target == world)
var/to_chat_filename
var/to_chat_line
var/to_chat_src
// Call using macro: to_chat(target, message, flag)
/proc/__to_chat(target, message, flag)
if(!is_valid_tochat_message(message) || !is_valid_tochat_target(target))
target << message
// Info about the "message"
if(isnull(message))
message = "(null)"
else if(istype(message, /datum))
var/datum/D = message
message = "([D.type]): '[D]'"
else if(!is_valid_tochat_message(message))
message = "(bad message) : '[message]'"
// Info about the target
var/targetstring = "'[target]'"
if(istype(target, /datum))
var/datum/D = target
targetstring += ", [D.type]"
// The final output
log_runtime(new/exception("DEBUG: to_chat called with invalid message/target.", to_chat_filename, to_chat_line), to_chat_src, list("Message: '[message]'", "Target: [targetstring]"))
return
else if(is_valid_tochat_message(message))
if(istext(target))
log_runtime(EXCEPTION("Somehow, to_chat got a text as a target"))
return
message = replacetext(message, "\n", "
")
message = macro2html(message)
if(findtext(message, "\improper"))
message = replacetext(message, "\improper", "")
if(findtext(message, "\proper"))
message = replacetext(message, "\proper", "")
var/client/C
if(istype(target, /client))
C = target
if(ismob(target))
C = target:client
if(C && C.chatOutput)
if(C.chatOutput.broken)
C << message
return
if(!C.chatOutput.loaded && C.chatOutput.messageQueue && islist(C.chatOutput.messageQueue))
C.chatOutput.messageQueue.Add(message)
return
// url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the javascript.
var/output_message = "[url_encode(url_encode(message))]"
if(flag)
output_message += "&[url_encode(flag)]"
target << output(output_message, "browseroutput:output")