mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-09 16:33:50 +00:00
* Makes bIcon scale to 16x16 like chat output did There was lag and ddos issues because to_chat does certain find replace operations on the resulting string, so putting base64 encoded data of large images in these strings would be insanely and excessively costly. This is basically banking on the assumption that md5 on a 480x480 image is less costly than 4 find/replaces and 2 html_encodes. If this doesn't work or we need larger icon support for some reason, I'll just move bIcon over to the asset cache system.
282 lines
8.5 KiB
Plaintext
282 lines
8.5 KiB
Plaintext
/*********************************
|
|
For the main html chat area
|
|
*********************************/
|
|
|
|
//Precaching a bunch of shit
|
|
GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of icons for the browser output
|
|
|
|
//On client, created on login
|
|
/datum/chatOutput
|
|
var/client/owner //client ref
|
|
var/loaded = FALSE // Has the client loaded the browser output area?
|
|
var/list/messageQueue //If they haven't loaded chat, this is where messages will go until they do
|
|
var/cookieSent = FALSE // Has the client sent a cookie for analysis
|
|
var/broken = FALSE
|
|
var/list/connectionHistory //Contains the connection history passed from chat cookie
|
|
|
|
/datum/chatOutput/New(client/C)
|
|
owner = C
|
|
messageQueue = list()
|
|
connectionHistory = list()
|
|
|
|
/datum/chatOutput/proc/start()
|
|
//Check for existing chat
|
|
if(!owner)
|
|
return FALSE
|
|
|
|
if(!winexists(owner, "browseroutput")) // Oh goddamnit.
|
|
set waitfor = FALSE
|
|
broken = TRUE
|
|
message_admins("Couldn't start chat for [key_name_admin(owner)]!")
|
|
. = FALSE
|
|
alert(owner.mob, "Updated chat window does not exist. If you are using a custom skin file please allow the game to update.")
|
|
return
|
|
|
|
if(winget(owner, "browseroutput", "is-visible") == "true") //Already setup
|
|
doneLoading()
|
|
|
|
else //Not setup
|
|
load()
|
|
|
|
return TRUE
|
|
|
|
/datum/chatOutput/proc/load()
|
|
set waitfor = FALSE
|
|
if(!owner)
|
|
return
|
|
|
|
var/datum/asset/stuff = get_asset_datum(/datum/asset/simple/goonchat)
|
|
stuff.register()
|
|
stuff.send(owner)
|
|
|
|
owner << browse(file('code/modules/goonchat/browserassets/html/browserOutput.html'), "window=browseroutput")
|
|
|
|
/datum/chatOutput/Topic(href, list/href_list)
|
|
if(usr.client != owner)
|
|
return TRUE
|
|
|
|
// Build arguments.
|
|
// Arguments are in the form "param[paramname]=thing"
|
|
var/list/params = list()
|
|
for(var/key in href_list)
|
|
if(length(key) > 7 && findtext(key, "param")) // 7 is the amount of characters in the basic param key template.
|
|
var/param_name = copytext(key, 7, -1)
|
|
var/item = href_list[key]
|
|
|
|
params[param_name] = item
|
|
|
|
var/data // Data to be sent back to the chat.
|
|
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)
|
|
|
|
|
|
//Called on chat output done-loading by JS.
|
|
/datum/chatOutput/proc/doneLoading()
|
|
if(loaded)
|
|
return
|
|
|
|
testing("Chat loaded for [owner.ckey]")
|
|
loaded = TRUE
|
|
showChat()
|
|
|
|
|
|
for(var/message in messageQueue)
|
|
to_chat(owner, message)
|
|
|
|
messageQueue = null
|
|
sendClientData()
|
|
|
|
//do not convert to to_chat()
|
|
owner << {"<span class="userdanger">If you can see this, update byond.</span>"}
|
|
|
|
pingLoop()
|
|
|
|
/datum/chatOutput/proc/showChat()
|
|
winset(owner, "output", "is-visible=false")
|
|
winset(owner, "browseroutput", "is-disabled=false;is-visible=true")
|
|
|
|
/datum/chatOutput/proc/pingLoop()
|
|
set waitfor = FALSE
|
|
|
|
while (owner)
|
|
ehjax_send(data = owner.is_afk(29) ? "softPang" : "pang") // SoftPang isn't handled anywhere but it'll always reset the opts.lastPang.
|
|
sleep(30)
|
|
|
|
/datum/chatOutput/proc/ehjax_send(client/C = owner, window = "browseroutput", data)
|
|
if(islist(data))
|
|
data = json_encode(data)
|
|
C << output("[data]", "[window]:ehjaxCallback")
|
|
|
|
//Sends client connection details to the chat to handle and save
|
|
/datum/chatOutput/proc/sendClientData()
|
|
//Get dem deets
|
|
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)
|
|
|
|
//Called by client, sent data to investigate (cookie history so far)
|
|
/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"] //lol fuck
|
|
var/list/found = new()
|
|
for(var/i in connectionHistory.len to 1 step -1)
|
|
var/list/row = src.connectionHistory[i]
|
|
if (!row || row.len < 3 || (!row["ckey"] || !row["compid"] || !row["ip"])) //Passed malformed history object
|
|
return
|
|
if (world.IsBanned(row["ckey"], row["compid"], row["ip"]))
|
|
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_private("[key_name(owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
|
|
|
|
cookieSent = TRUE
|
|
|
|
//Called by js client every 60 seconds
|
|
/datum/chatOutput/proc/ping()
|
|
return "pong"
|
|
|
|
//Called by js client on js error
|
|
/datum/chatOutput/proc/debug(error)
|
|
log_world("\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client: [(src.owner.key ? src.owner.key : src.owner)] triggered JS error: [error]")
|
|
|
|
//Global chat procs
|
|
|
|
//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(icon/icon, iconKey = "misc")
|
|
if (!isicon(icon))
|
|
return FALSE
|
|
GLOB.iconCache[iconKey] << icon
|
|
var/iconData = GLOB.iconCache.ExportText(iconKey)
|
|
var/list/partial = splittext(iconData, "{")
|
|
return replacetext(copytext(partial[2], 3, -5), "\n", "")
|
|
|
|
/proc/bicon(thing)
|
|
if (!thing)
|
|
return
|
|
var/static/list/bicon_cache = list()
|
|
if (isicon(thing))
|
|
var/icon/I = thing
|
|
var/icon_md5 = md5(I)
|
|
if (!bicon_cache[icon_md5]) // Doesn't exist yet, make it.
|
|
I = icon(I) //copy it
|
|
I.Scale(16,16) //scale it
|
|
bicon_cache[icon_md5] = icon2base64(thing) //base64 it
|
|
return "<img class='icon misc' src='data:image/png;base64,[bicon_cache[icon_md5]]'>"
|
|
|
|
// Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with.
|
|
var/atom/A = thing
|
|
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)
|
|
I.Scale(16,16)
|
|
if (ishuman(thing)) // Shitty workaround for a BYOND issue.
|
|
var/icon/temp = I
|
|
I = icon()
|
|
I.Insert(temp, dir = SOUTH)
|
|
bicon_cache[key] = icon2base64(I, key)
|
|
|
|
return "<img class='icon [A.icon_state]' src='data:image/png;base64,[bicon_cache[key]]'>"
|
|
|
|
//Costlier version of bicon() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs.
|
|
/proc/costly_bicon(thing)
|
|
if (!thing)
|
|
return
|
|
|
|
if (isicon(thing))
|
|
return bicon(thing)
|
|
|
|
var/icon/I = getFlatIcon(thing)
|
|
return bicon(I)
|
|
|
|
/proc/to_chat(target, message)
|
|
if(!target)
|
|
return
|
|
|
|
//Ok so I did my best but I accept that some calls to this will be for shit like sound and images
|
|
//It stands that we PROBABLY don't want to output those to the browser output so just handle them here
|
|
if (istype(message, /image) || istype(message, /sound) || istype(target, /savefile))
|
|
target << message
|
|
CRASH("Invalid message! [message]")
|
|
|
|
if(!istext(message))
|
|
return
|
|
|
|
if(target == world)
|
|
target = GLOB.clients
|
|
|
|
var/list/targets
|
|
if(!islist(target))
|
|
targets = list(target)
|
|
else
|
|
targets = target
|
|
if(!targets.len)
|
|
return
|
|
var/original_message = message
|
|
//Some macros remain in the string even after parsing and fuck up the eventual output
|
|
message = replacetext(message, "\improper", "")
|
|
message = replacetext(message, "\proper", "")
|
|
message = replacetext(message, "\n", "<br>")
|
|
message = replacetext(message, "\t", "[GLOB.TAB][GLOB.TAB]")
|
|
|
|
for(var/I in targets)
|
|
//Grab us a client if possible
|
|
var/client/C = grab_client(I)
|
|
|
|
if (!C)
|
|
continue
|
|
|
|
//Send it to the old style output window.
|
|
C << original_message
|
|
|
|
if(!C.chatOutput || C.chatOutput.broken) // A player who hasn't updated his skin file.
|
|
continue
|
|
|
|
if(!C.chatOutput.loaded)
|
|
//Client still loading, put their messages in a queue
|
|
C.chatOutput.messageQueue += message
|
|
continue
|
|
|
|
// url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
|
|
C << output(url_encode(url_encode(message)), "browseroutput:output")
|
|
|
|
/proc/grab_client(target)
|
|
if(istype(target, /client))
|
|
return target
|
|
else if(ismob(target))
|
|
var/mob/M = target
|
|
if(M.client)
|
|
return M.client
|
|
else if(istype(target, /datum/mind))
|
|
var/datum/mind/M = target
|
|
if(M.current && M.current.client)
|
|
return M.current.client
|