mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-19 03:55:11 +01:00
cd966fcdd2
## About The Pull Request Ports https://github.com/Monkestation/Monkestation2.0/pull/5744, alongside the metadata stuff from https://github.com/Monkestation/Monkestation2.0/pull/11617 and https://github.com/Monkestation/Monkestation2.0/pull/11644 > This PR adds a websocket client to the chatbox, that redirects all incoming payloads from the server, to the specified server, which allows for external integrations with the game. All of which is parsable JSON. > > Some ideas include RGB feedback, triggering an event in VTubing software, or automating a physical device based on what's sent to the chat. (For example, a haptic suit, and have a regex for "in the right leg" or any body part) The websocket can also be used to request basic "metadata" about the current round - server name, station name, round id, map name, round duration, and game state (all this information can be obtained via the `status` world topic without being logged in anyways, to be clear) - if the server has webroot set up, it will also send the webroot base url and the links to the chat assets (i added this just so i could embed the chat styling and icons in my chat log html) **To be clear, the websocket is entirely client-side, if it's not obvious.** <details> <summary>Example of metadata JSON</summary> ```json { "type": "metadata", "payload": { "game_version": "/tg/Station 13", "server_name": "kbity station 13 (tg)", "round_id": "152", "map_name": "Minimal Runtime Station", "round_duration": 6, "gamestate": 3 } } ``` </details> <sub>if y'all make a freaky joke i will beat you with a rock, I do not care about _that_ kind of thing in the slightest.</sub> ## Why It's Good For The Game Lots of interesting things. I've made a doohickey that allows me to automatically save my chat logs instead of having to manually click "Save Chat Log" in settings repeatedly (which is also limited by the chat scrollback limit) "i have had a recurring thought of adding websocket support so I can swap my spotify playlists whenever I get into combat on lavaland" - smartkar ## Screenshots <img width="676" height="158" alt="2026-05-27 (1779927348) ~ dreamseeker" src="https://github.com/user-attachments/assets/28185ddd-b6b6-423e-9b46-cfaa519972ac" /> <img width="2298" height="608" alt="2026-05-27 (1779927386) ~ WindowsTerminal" src="https://github.com/user-attachments/assets/1e41b783-6c43-4399-9ab5-a9c8bf67d117" /> <img width="1651" height="391" alt="2026-05-27 (1779927395) ~ dreamseeker" src="https://github.com/user-attachments/assets/5e905670-edd6-4efc-8d0c-3d19e39799a6" /> ## Changelog 🆑 Absolucy, Flleeppyy add: Added a websocket client, you can now forward incoming chat messages to an external program, i.e allowing you to change music playlists based on events, or such. /🆑
146 lines
4.0 KiB
Plaintext
146 lines
4.0 KiB
Plaintext
/*!
|
|
* Copyright (c) 2020 Aleksej Komarov
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/**
|
|
* tgui_panel datum
|
|
* Hosts tgchat and other nice features.
|
|
*/
|
|
/datum/tgui_panel
|
|
var/client/client
|
|
var/datum/tgui_window/window
|
|
var/broken = FALSE
|
|
var/initialized_at
|
|
/// Each client notifies on protected playback, so this prevents spamming admins.
|
|
var/static/admins_warned = FALSE
|
|
|
|
/datum/tgui_panel/New(client/client, id)
|
|
src.client = client
|
|
window = new(client, id)
|
|
window.subscribe(src, PROC_REF(on_message))
|
|
|
|
/datum/tgui_panel/Del()
|
|
window.unsubscribe(src)
|
|
window.close()
|
|
return ..()
|
|
|
|
/**
|
|
* public
|
|
*
|
|
* TRUE if panel is initialized and ready to receive messages.
|
|
*/
|
|
/datum/tgui_panel/proc/is_ready()
|
|
return !broken && window.is_ready()
|
|
|
|
/**
|
|
* public
|
|
*
|
|
* Initializes tgui panel.
|
|
*/
|
|
/datum/tgui_panel/proc/initialize(force = FALSE)
|
|
set waitfor = FALSE
|
|
// Minimal sleep to defer initialization to after client constructor
|
|
sleep(1 TICKS)
|
|
initialized_at = world.time
|
|
// Perform a clean initialization
|
|
window.initialize(
|
|
strict_mode = TRUE,
|
|
assets = list(
|
|
get_asset_datum(/datum/asset/simple/tgui_panel),
|
|
))
|
|
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
|
|
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
|
|
window.send_asset(get_asset_datum(/datum/asset/spritesheet_batched/chat))
|
|
// Other setup
|
|
request_telemetry()
|
|
addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS)
|
|
window.send_message("testTelemetryCommand")
|
|
|
|
/**
|
|
* private
|
|
*
|
|
* Called when initialization has timed out.
|
|
*/
|
|
/datum/tgui_panel/proc/on_initialize_timed_out()
|
|
// Currently does nothing but sending a message to old chat.
|
|
SEND_TEXT(client, span_userdanger("Failed to load fancy chat, click <a href='byond://?src=[REF(src)];reload_tguipanel=1'>HERE</a> to attempt to reload it."))
|
|
|
|
/**
|
|
* private
|
|
*
|
|
* Callback for handling incoming tgui messages.
|
|
*/
|
|
/datum/tgui_panel/proc/on_message(type, payload)
|
|
if(type == "ready")
|
|
broken = FALSE
|
|
window.send_message("update", list(
|
|
"config" = list(
|
|
"client" = list(
|
|
"ckey" = client.ckey,
|
|
"address" = client.address,
|
|
"computer_id" = client.computer_id,
|
|
),
|
|
"window" = list(
|
|
"locked" = FALSE,
|
|
),
|
|
),
|
|
))
|
|
return TRUE
|
|
|
|
if(type == "audio/setAdminMusicVolume")
|
|
client.admin_music_volume = payload["volume"]
|
|
return TRUE
|
|
|
|
if(type == "audio/protected")
|
|
if(!admins_warned)
|
|
message_admins(span_notice("Audio returned a protected playback error, likely due to being copyrighted."))
|
|
admins_warned = TRUE
|
|
addtimer(VARSET_CALLBACK(src, admins_warned, FALSE), 10 SECONDS)
|
|
return TRUE
|
|
|
|
if(type == "telemetry")
|
|
analyze_telemetry(payload)
|
|
return TRUE
|
|
|
|
if(type == "requestMetadata")
|
|
send_metadata()
|
|
return TRUE
|
|
|
|
/**
|
|
* public
|
|
*
|
|
* Sends a round restart notification.
|
|
*/
|
|
/datum/tgui_panel/proc/send_roundrestart()
|
|
window.send_message("roundrestart")
|
|
|
|
/**
|
|
* private
|
|
*
|
|
* Sent when a client requests metadata - used for websocket stuff.
|
|
*/
|
|
/datum/tgui_panel/proc/send_metadata()
|
|
var/static/list/webroot_asset_urls
|
|
|
|
var/list/metadata = list(
|
|
"game_version" = GLOB.game_version,
|
|
"server_name" = CONFIG_GET(string/servername),
|
|
"round_id" = GLOB.round_id,
|
|
"map_name" = SSmapping.current_map?.map_name,
|
|
"round_duration" = round(STATION_TIME_PASSED() / 10, 1),
|
|
"gamestate" = SSticker.current_state,
|
|
)
|
|
// if we're using webroot - also pass along the webroot url and such, so we can embed chat logs with the proper styles/images if desired
|
|
if(istype(SSassets.transport, /datum/asset_transport/webroot))
|
|
if(isnull(webroot_asset_urls))
|
|
webroot_asset_urls = list()
|
|
for(var/asset_type in list(/datum/asset/simple/tgui_panel, /datum/asset/simple/namespaced/fontawesome, /datum/asset/simple/namespaced/tgfont, /datum/asset/spritesheet_batched/chat))
|
|
var/datum/asset/asset = get_asset_datum(asset_type)
|
|
webroot_asset_urls += asset.get_url_mappings()
|
|
metadata["webroot"] = list(
|
|
"base_url" = CONFIG_GET(string/asset_cdn_url),
|
|
"assets" = webroot_asset_urls,
|
|
)
|
|
window.send_message("metadata", metadata)
|