From bcd5f73e9ab1c8c2bb520910229d93ca33c45a39 Mon Sep 17 00:00:00 2001 From: Brandon Sturgeon Date: Wed, 20 Sep 2023 18:51:30 -0700 Subject: [PATCH] Feature/larynx (#40) --- .gitignore | 1 + lua/autorun/client/cfc_chat_transit_init.lua | 1 + lua/autorun/server/sv_chat_transit_init.lua | 6 + lua/cfc_chat_transit/client/init.lua | 2 + lua/cfc_chat_transit/client/menu.lua | 31 ++++ .../client/receive_remote_message.lua | 73 ++++++++ .../server/avatar_service.lua | 90 ++++++++++ lua/cfc_chat_transit/server/init.lua | 89 ++++++++++ .../server/modules/anticrash.lua | 37 ++++ lua/cfc_chat_transit/server/modules/chat.lua | 34 ++++ .../server/modules/connect.lua | 48 +++++ .../server/modules/disconnect.lua | 32 ++++ lua/cfc_chat_transit/server/modules/pvp.lua | 17 ++ .../server/modules/startup.lua | 26 +++ lua/cfc_chat_transit/server/modules/ulx.lua | 40 +++++ lua/cfc_chat_transit/server/modules/voice.lua | 42 +++++ lua/cfc_chat_transit/server/player_count.lua | 19 ++ .../server/remote_messages.lua | 58 ++++++ .../server/avatar_service.moon | 8 +- moon/cfc_chat_transit/server/init.moon | 6 +- .../server/modules/voice.moon | 30 ++++ .../{Dockerfile.service => Dockerfile} | 13 +- web/avatar_service/docker-compose.yml | 3 +- web/avatar_service/entrypoint.sh | 0 web/discord_relay/Dockerfile | 4 + web/discord_relay/docker-compose.yml | 1 + web/discord_relay/go.mod | 3 +- web/discord_relay/go.sum | 11 ++ web/discord_relay/queue.go | 112 +++++++++++- web/discord_relay/voice/voice.go | 166 ++++++++++++++++++ 30 files changed, 981 insertions(+), 22 deletions(-) create mode 100644 lua/autorun/client/cfc_chat_transit_init.lua create mode 100644 lua/autorun/server/sv_chat_transit_init.lua create mode 100644 lua/cfc_chat_transit/client/init.lua create mode 100644 lua/cfc_chat_transit/client/menu.lua create mode 100644 lua/cfc_chat_transit/client/receive_remote_message.lua create mode 100644 lua/cfc_chat_transit/server/avatar_service.lua create mode 100644 lua/cfc_chat_transit/server/init.lua create mode 100644 lua/cfc_chat_transit/server/modules/anticrash.lua create mode 100644 lua/cfc_chat_transit/server/modules/chat.lua create mode 100644 lua/cfc_chat_transit/server/modules/connect.lua create mode 100644 lua/cfc_chat_transit/server/modules/disconnect.lua create mode 100644 lua/cfc_chat_transit/server/modules/pvp.lua create mode 100644 lua/cfc_chat_transit/server/modules/startup.lua create mode 100644 lua/cfc_chat_transit/server/modules/ulx.lua create mode 100644 lua/cfc_chat_transit/server/modules/voice.lua create mode 100644 lua/cfc_chat_transit/server/player_count.lua create mode 100644 lua/cfc_chat_transit/server/remote_messages.lua create mode 100644 moon/cfc_chat_transit/server/modules/voice.moon rename web/avatar_service/{Dockerfile.service => Dockerfile} (70%) mode change 100644 => 100755 web/avatar_service/entrypoint.sh create mode 100644 web/discord_relay/voice/voice.go diff --git a/.gitignore b/.gitignore index 4c49bd7..e58e5a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .env +.idea diff --git a/lua/autorun/client/cfc_chat_transit_init.lua b/lua/autorun/client/cfc_chat_transit_init.lua new file mode 100644 index 0000000..64f70ae --- /dev/null +++ b/lua/autorun/client/cfc_chat_transit_init.lua @@ -0,0 +1 @@ +return include("cfc_chat_transit/client/init.lua") diff --git a/lua/autorun/server/sv_chat_transit_init.lua b/lua/autorun/server/sv_chat_transit_init.lua new file mode 100644 index 0000000..af72f1c --- /dev/null +++ b/lua/autorun/server/sv_chat_transit_init.lua @@ -0,0 +1,6 @@ +require("steamlookup") +include("cfc_chat_transit/server/init.lua") +include("cfc_chat_transit/server/remote_messages.lua") +AddCSLuaFile("cfc_chat_transit/client/init.lua") +AddCSLuaFile("cfc_chat_transit/client/menu.lua") +return AddCSLuaFile("cfc_chat_transit/client/receive_remote_message.lua") diff --git a/lua/cfc_chat_transit/client/init.lua b/lua/cfc_chat_transit/client/init.lua new file mode 100644 index 0000000..1aa8e2c --- /dev/null +++ b/lua/cfc_chat_transit/client/init.lua @@ -0,0 +1,2 @@ +ChatTransit = { } +return include("receive_remote_message.lua") diff --git a/lua/cfc_chat_transit/client/menu.lua b/lua/cfc_chat_transit/client/menu.lua new file mode 100644 index 0000000..b0d53f8 --- /dev/null +++ b/lua/cfc_chat_transit/client/menu.lua @@ -0,0 +1,31 @@ +local alertPreference +alertPreference = function(val) + net.Start("CFC_ChatTransit_RemoteMessagePreference") + net.WriteBool(val) + return net.SendToServer() +end +local initHookName = "CFC_ChatTransit_AlertRemoteMessagePreference" +hook.Add("Think", initHookName, function() + hook.Remove("Think", initHookName) + alertPreference(ChatTransit.shouldReceiveRemoteMessages:GetBool()) + return nil +end) +local populatePanel +populatePanel = function(panel) + local label = "Should show remote messages" + do + local _with_0 = panel:CheckBox(label, "cfc_chat_transit_remote_messages") + _with_0.OnChange = function(_, val) + return ChatTransit.alertPreference(val) + end + return _with_0 + end +end +hook.Add("AddToolMenuCategories", "CFC_ChatTransit_MenuCategory", function() + return AddToolCategory("Options", "CFC", "CFC") +end) +return hook.Add("PopulateToolMenu", "CFC_ChatTransit_MenuOption", function() + return AddToolMenuOption("Options", "CFC", "should_receive_remote_messages", "Remote Messages", "", "", function(panel) + return populatePanel(panel) + end) +end) diff --git a/lua/cfc_chat_transit/client/receive_remote_message.lua b/lua/cfc_chat_transit/client/receive_remote_message.lua new file mode 100644 index 0000000..4139add --- /dev/null +++ b/lua/cfc_chat_transit/client/receive_remote_message.lua @@ -0,0 +1,73 @@ +local Start, Receive, ReadBool, ReadColor, ReadString, WriteBool, SendToServer +do + local _obj_0 = net + Start, Receive, ReadBool, ReadColor, ReadString, WriteBool, SendToServer = _obj_0.Start, _obj_0.Receive, _obj_0.ReadBool, _obj_0.ReadColor, _obj_0.ReadString, _obj_0.WriteBool, _obj_0.SendToServer +end +local AddToolCategory, AddToolMenuOption +do + local _obj_0 = spawnmenu + AddToolCategory, AddToolMenuOption = _obj_0.AddToolCategory, _obj_0.AddToolMenuOption +end +local shouldReceiveRemoteMessages = CreateConVar("cfc_chat_transit_remote_messages", 1, FCVAR_ARCHIVE, "Should receive remote messges in chat", 0, 1) +local colors = { + white = Color(255, 255, 255), + blurple = Color(142, 163, 247) +} +Receive("CFC_ChatTransit_RemoteMessageReceive", function() + if not (shouldReceiveRemoteMessages:GetBool()) then + return + end + local author = ReadString() + local authorColor = ReadColor() + local message = ReadString() + if not (author) then + return + end + if not (authorColor) then + return + end + if not (message) then + return + end + local addTextParams = { + colors.blurple, + "[Discord] ", + authorColor, + author, + colors.white, + ": " .. tostring(message) + } + hook.Run("CFC_ChatTransit_RemoteMessageReceive", addTextParams) + return chat.AddText(unpack(addTextParams)) +end) +local alertPreference +alertPreference = function(val) + Start("CFC_ChatTransit_RemoteMessagePreference") + WriteBool(val) + return SendToServer() +end +local initHookName = "CFC_ChatTransit_AlertRemoteMessagePreference" +hook.Add("Think", initHookName, function() + hook.Remove("Think", initHookName) + alertPreference(shouldReceiveRemoteMessages:GetBool()) + return nil +end) +local populatePanel +populatePanel = function(panel) + local label = "Should show remote messages" + do + local _with_0 = panel:CheckBox(label, "cfc_chat_transit_remote_messages") + _with_0.OnChange = function(_, val) + return alertPreference(val) + end + return _with_0 + end +end +hook.Add("AddToolMenuCategories", "CFC_ChatTransit_MenuCategory", function() + return AddToolCategory("Options", "CFC", "CFC") +end) +return hook.Add("PopulateToolMenu", "CFC_ChatTransit_MenuOption", function() + return AddToolMenuOption("Options", "CFC", "should_receive_remote_messages", "Remote Messages", "", "", function(panel) + return populatePanel(panel) + end) +end) diff --git a/lua/cfc_chat_transit/server/avatar_service.lua b/lua/cfc_chat_transit/server/avatar_service.lua new file mode 100644 index 0000000..ec4364c --- /dev/null +++ b/lua/cfc_chat_transit/server/avatar_service.lua @@ -0,0 +1,90 @@ +local TableToJSON +TableToJSON = util.TableToJSON +local HTTP = HTTP +local avatarServiceAddress = CreateConVar("cfc_avatar_service_address", "", FCVAR_ARCHIVE + FCVAR_PROTECTED) +local AvatarService +do + local _class_0 + local _base_0 = { + getAvatar = function(self, steamID64) + local url = steamID64 and "https://avatarservice.cfcservers.org/avatars/" .. tostring(steamID64) .. ".png" or nil + if self.processedIds[steamID64] then + url = url and tostring(url) .. "?processed=true" + end + return url + end, + processAvatar = function(self, avatarUrl, outlineColor, steamID64) + local body = TableToJSON({ + avatarUrl = avatarUrl, + outlineColor = outlineColor, + steamID = steamID64 + }) + self.logger:debug("Sending data to outliner: ", body) + local failed + do + local _base_1 = self.logger + local _fn_0 = _base_1.error + failed = function(...) + return _fn_0(_base_1, ...) + end + end + local success + success = function(code, body) + self.logger:debug("Avatar request succeeded with code: " .. tostring(code) .. " | Body: " .. tostring(body)) + self.processedIds[steamID64] = true + end + return HTTP({ + success = success, + failed = failed, + body = body, + url = self.outlinerUrl, + method = "POST", + type = "application/json" + }) + end, + outlineAvatar = function(self, ply, data) + self.logger:debug("Received request to outline avatar for ply: " .. tostring(ply:Nick())) + local avatar = data.response.players[1].avatarfull + local outlineColor = ChatTransit:GetTeamColor(ply:Team()) + local steamID64 = ply:SteamID64() + return self:processAvatar(avatar, outlineColor, steamID64) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, logger) + self.logger = logger:scope("AvatarService") + self.outlinerUrl = tostring(avatarServiceAddress:GetString()) .. "/outline" + self.processedIds = { } + end, + __base = _base_0, + __name = "AvatarService" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + AvatarService = _class_0 +end +hook.Add("InitPostEntity", "CFC_ChatTrahsit_AvatarServiceInit", function() + ChatTransit.AvatarService = AvatarService(ChatTransit.Logger) +end) +return hook.Add("CFC_SteamLookup_SuccessfulPlayerData", "CFC_ChatTransit_AvatarService", function(dataName, ply, data) + if not (dataName == "PlayerSummary") then + return + end + if not (data) then + return + end + local success, err = pcall(function() + return ChatTransit.AvatarService:outlineAvatar(ply, data) + end) + if not (success) then + ErrorNoHaltWithStack(err, dataName, ply) + end + return nil +end) diff --git a/lua/cfc_chat_transit/server/init.lua b/lua/cfc_chat_transit/server/init.lua new file mode 100644 index 0000000..0842d6a --- /dev/null +++ b/lua/cfc_chat_transit/server/init.lua @@ -0,0 +1,89 @@ +require("gwsockets") +require("logger") +ChatTransit = { + Logger = Logger("ChatTransit") +} +include("cfc_chat_transit/server/avatar_service.lua") +include("cfc_chat_transit/server/player_count.lua") +local Read +Read = file.Read +local GetColor +GetColor = team.GetColor +local TableToJSON +TableToJSON = util.TableToJSON +local logger = ChatTransit.Logger +local relayHost = CreateConVar("cfc_relay_host", "", FCVAR_NONE) +local loadHook = "ChatTransit_WebsocketLoad" +hook.Add("Think", loadHook, function() + hook.Remove("Think", loadHook) + ChatTransit.WebSocket = GWSockets.createWebSocket("wss://" .. tostring(relayHost:GetString()) .. "/relay", false) + ChatTransit.Realm = CreateConVar("cfc_realm", "unknown", FCVAR_REPLICATED + FCVAR_ARCHIVE, "The Realm Name") + do + local _with_0 = ChatTransit.WebSocket + _with_0.reconnectTimerName = "CFC_ChatTransit_WebsocketReconnect" + _with_0.onConnected = function(self) + logger:info("Established websocket connection") + return timer.Remove(_with_0.reconnectTimerName) + end + _with_0.onDisconnected = function(self) + logger:warn("Lost websocket connection!") + if timer.Exists(_with_0.reconnectTimerName) then + return logger:warn("Will retry " .. tostring(timer.RepsLeft(_with_0.reconnectTimerName)) .. " more times") + end + return timer.Create(_with_0.reconnectTimerName, 2, 30, function() + return _with_0:open() + end) + end + _with_0.onError = function(self, message) + return logger:error("Websocket Error!", message) + end + _with_0:open() + end + return nil +end) +ChatTransit.Send = function(self, data) + logger:debug("Sending '" .. tostring(data.Type) .. "'") + local steamID64 = data.Data.SteamId + data.Data.Avatar = data.Data.Avatar or ChatTransit.AvatarService:getAvatar(steamID64) + data.Realm = self.Realm:GetString() + data.Data.SteamId = data.Data.SteamId or "" + return self.WebSocket:write(TableToJSON(data)) +end +ChatTransit.TeamColorCache = { } +ChatTransit.GetTeamColor = function(self, teamName) + if self.TeamColorCache[teamName] then + return self.TeamColorCache[teamName] + end + local teamColor = tostring(GetColor(teamName)) + self.TeamColorCache[teamName] = teamColor + return teamColor +end +ChatTransit.guard = function(f, delay) + return function(...) + local args = { + ... + } + local action + action = function() + local success, err = pcall(function() + return f(unpack(args)) + end) + if not (success) then + return ErrorNoHaltWithStack(err) + end + end + if delay then + timer.Simple(delay, action) + else + action() + end + return nil + end +end +logger:info("Loading modules...") +local _list_0 = file.Find("cfc_chat_transit/server/modules/*.lua", "LUA") +for _index_0 = 1, #_list_0 do + local f = _list_0[_index_0] + logger:info("Loading modules/" .. tostring(f)) + include("modules/" .. tostring(f)) +end diff --git a/lua/cfc_chat_transit/server/modules/anticrash.lua b/lua/cfc_chat_transit/server/modules/anticrash.lua new file mode 100644 index 0000000..c2a10f5 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/anticrash.lua @@ -0,0 +1,37 @@ +local guard +guard = ChatTransit.guard +local isstring +isstring = _G.isstring +ChatTransit.AnticrashEvent = function(self, eventText) + if not (isstring(eventText)) then + eventText = "Heavy lag detected!" + end + return self:Send({ + Type = "anticrash_event", + Data = { + Content = eventText, + SteamName = "CFC Anticrash" + } + }) +end +hook.Add("z_anticrash_LagDetect", "CFC_ChatTransit_AnticrashEventListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.AnticrashEvent + return function(...) + return _fn_0(_base_0, ...) + end +end)())) +hook.Add("z_anticrash_LagStuck", "CFC_ChatTransit_AnticrashEventListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.AnticrashEvent + return function(...) + return _fn_0(_base_0, ...) + end +end)())) +return hook.Add("z_anticrash_CrashPrevented", "CFC_ChatTransit_AnticrashEventListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.AnticrashEvent + return function(...) + return _fn_0(_base_0, ...) + end +end)())) diff --git a/lua/cfc_chat_transit/server/modules/chat.lua b/lua/cfc_chat_transit/server/modules/chat.lua new file mode 100644 index 0000000..c3c7264 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/chat.lua @@ -0,0 +1,34 @@ +local guard +guard = ChatTransit.guard +ChatTransit.ReceiveMessage = function(self, ply, text, teamChat) + local shouldRelay = hook.Run("CFC_ChatTransit_ShouldRelayChatMessage", ply, text, teamChat) + if shouldRelay == false then + return + end + if teamChat then + return + end + if not (text) then + return + end + if text == "" then + return + end + return self:Send({ + Type = "message", + Data = { + Type = "message", + Content = text, + SteamName = ply:Nick(), + SteamId = ply:SteamID64(), + IrisId = "none" + } + }) +end +return hook.Add("PlayerSay", "CFC_ChatTransit_MessageListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.ReceiveMessage + return function(...) + return _fn_0(_base_0, ...) + end +end)()), HOOK_MONITOR_LOW) diff --git a/lua/cfc_chat_transit/server/modules/connect.lua b/lua/cfc_chat_transit/server/modules/connect.lua new file mode 100644 index 0000000..308c0e4 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/connect.lua @@ -0,0 +1,48 @@ +local delay, guard +do + local _obj_0 = ChatTransit + delay, guard = _obj_0.delay, _obj_0.guard +end +local SteamIDTo64 +SteamIDTo64 = util.SteamIDTo64 +ChatTransit.PlayerConnect = function(self, data) + local bot, name, steamId + bot, name, steamId = data.bot, data.name, data.networkid + bot = tobool(bot) + if bot then + steamId = nil + end + return self:Send({ + Type = "connect", + Data = { + SteamName = name, + SteamId = steamId and SteamIDTo64(steamId), + PlayerCountCurrent = ChatTransit.playerCount, + PlayerCountMax = game:MaxPlayers() + } + }) +end +ChatTransit.PlayerInitialSpawn = function(self, ply) + return self:Send({ + Type = "spawn", + Data = { + SteamName = ply:Nick(), + SteamId = ply:SteamID64() + } + }) +end +gameevent.Listen("player_connect") +hook.Add("player_connect", "CFC_ChatTransit_SpawnListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.PlayerConnect + return function(...) + return _fn_0(_base_0, ...) + end +end)(), 0)) +return hook.Add("PlayerInitialSpawn", "CFC_ChatTransit_SpawnListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.PlayerInitialSpawn + return function(...) + return _fn_0(_base_0, ...) + end +end)())) diff --git a/lua/cfc_chat_transit/server/modules/disconnect.lua b/lua/cfc_chat_transit/server/modules/disconnect.lua new file mode 100644 index 0000000..14296bc --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/disconnect.lua @@ -0,0 +1,32 @@ +local delay, guard +do + local _obj_0 = ChatTransit + delay, guard = _obj_0.delay, _obj_0.guard +end +local GetBySteamID +GetBySteamID = player.GetBySteamID +local SteamIDTo64 +SteamIDTo64 = util.SteamIDTo64 +ChatTransit.PlayerDisconnected = function(self, data) + local name, reason, steamId + name, reason, steamId = data.name, data.reason, data.networkid + local ply = GetBySteamID(steamId) + return self:Send({ + Type = "disconnect", + Data = { + SteamName = ply and ply:Nick() or name, + SteamId = ply and ply:SteamID64() or SteamIDTo64(steamId), + PlayerCountCurrent = ChatTransit.playerCount, + PlayerCountMax = game:MaxPlayers(), + Content = reason + } + }) +end +gameevent.Listen("player_disconnect") +return hook.Add("player_disconnect", "CFC_ChatTransit_DisconnectListener", guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.PlayerDisconnected + return function(...) + return _fn_0(_base_0, ...) + end +end)(), 0)) diff --git a/lua/cfc_chat_transit/server/modules/pvp.lua b/lua/cfc_chat_transit/server/modules/pvp.lua new file mode 100644 index 0000000..e8216b1 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/pvp.lua @@ -0,0 +1,17 @@ +local guard +guard = ChatTransit.guard +ChatTransit.PvPEvent = function(self, ply, newMode) + local eventText = tostring(ply:Nick()) .. " has entered " .. tostring(newMode) .. " mode" + return self:Send({ + Type = "pvp_status_change", + Data = { + Content = eventText + } + }) +end +hook.Add("CFC_PvP_PlayerEnterPvp", "CFC_ChatTransit_Relay", guard(function(ply) + return ChatTransit:PvPEvent(ply, "PvP") +end)) +return hook.Add("CFC_PvP_PlayerExitPvp", "CFC_ChatTransit_Relay", guard(function(ply) + return ChatTransit:PvPEvent(ply, "Build") +end)) diff --git a/lua/cfc_chat_transit/server/modules/startup.lua b/lua/cfc_chat_transit/server/modules/startup.lua new file mode 100644 index 0000000..031767a --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/startup.lua @@ -0,0 +1,26 @@ +local guard +guard = ChatTransit.guard +local GetMap +GetMap = game.GetMap +ChatTransit.MapStartup = function(self, data) + local eventText = "" + local map = GetMap() + if SysTime() > 500 then + eventText = "Map switched to " .. map + else + eventText = "Server restarted! Current map is " .. map + end + return self:Send({ + Type = "map_init", + Data = { + Content = eventText + } + }) +end +return hook.Add("InitPostEntity", "CFC_ChatTransit_StartListener", timer.Simple(1, guard((function() + local _base_0 = ChatTransit + local _fn_0 = _base_0.MapStartup + return function(...) + return _fn_0(_base_0, ...) + end +end)()))) diff --git a/lua/cfc_chat_transit/server/modules/ulx.lua b/lua/cfc_chat_transit/server/modules/ulx.lua new file mode 100644 index 0000000..7a09cd5 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/ulx.lua @@ -0,0 +1,40 @@ +_Msg = Msg +local guard +guard = ChatTransit.guard +local isstring +isstring = _G.isstring +local Replace +Replace = string.Replace +ChatTransit.ReceiveULXAction = function(self, msg) + msg = Replace(msg, "\n", "") + self.Logger:debug("Received global ULX message", msg) + return self:Send({ + Type = "ulx_action", + Data = { + Type = "ulx_action", + Content = msg + } + }) +end +local M +M = function(...) + _Msg(...) + return ChatTransit:ReceiveULXAction(...) +end +return hook.Add("InitPostEntity", "ChatTransit_WrapUlxLog", guard(function() + if not (ulx) then + return + end + ulx._ChatTransit_fancyLogAdmin = ulx._ChatTransit_fancyLogAdmin or ulx.fancyLogAdmin + ulx.fancyLogAdmin = function(...) + local args = { + ... + } + if not (isstring(args[2])) then + return ulx._ChatTransit_fancyLogAdmin(...) + end + Msg = M + ulx._ChatTransit_fancyLogAdmin(...) + Msg = _Msg + end +end)) diff --git a/lua/cfc_chat_transit/server/modules/voice.lua b/lua/cfc_chat_transit/server/modules/voice.lua new file mode 100644 index 0000000..9fcb184 --- /dev/null +++ b/lua/cfc_chat_transit/server/modules/voice.lua @@ -0,0 +1,42 @@ +local IsValid +IsValid = _G.IsValid +local guard +guard = ChatTransit.guard +local proxVoice +local proxVoiceEnabled +proxVoiceEnabled = function() + proxVoice = proxVoice or GetConVar("force_proximity_voice") + if not (proxVoice) then + return false + end + return proxVoice:GetBool() +end +ChatTransit.ReceiveVoiceTranscript = function(self, steamID64, data) + if proxVoiceEnabled() then + return + end + local ply = player.GetBySteamID64(steamID64) + if not (IsValid(ply)) then + return + end + if ply.ulx_gagged then + return + end + if ply:GetInfoNum("proximity_voice_enabled", 0) ~= 0 then + return + end + local shouldRelay = hook.Run("CFC_ChatTransit_ShouldRelayTranscript", ply, transcript) + if shouldRelay == false then + return + end + return self:Send({ + Type = "voice_transcript", + Data = { + Type = "voice_transcript", + Content = data, + SteamName = ply:Nick(), + SteamId = ply:SteamID64(), + IrisId = "none" + } + }) +end diff --git a/lua/cfc_chat_transit/server/player_count.lua b/lua/cfc_chat_transit/server/player_count.lua new file mode 100644 index 0000000..2006faa --- /dev/null +++ b/lua/cfc_chat_transit/server/player_count.lua @@ -0,0 +1,19 @@ +ChatTransit.playerCount = 0 +local increment +increment = function() + ChatTransit.playerCount = ChatTransit.playerCount + 1 +end +local decrement +decrement = function() + ChatTransit.playerCount = ChatTransit.playerCount - 1 +end +hook.Add("ClientSignOnStateChanged", "ChatTransit_PlayerCount", function(_, oldstate) + if not (oldstate == 7) then + return + end + return increment() +end) +gameevent.Listen("player_connect") +hook.Add("player_connect", "ChatTransit_PlayerCount", increment) +gameevent.Listen("player_disconnect") +return hook.Add("player_disconnect", "ChatTransit_PlayerCount", decrement) diff --git a/lua/cfc_chat_transit/server/remote_messages.lua b/lua/cfc_chat_transit/server/remote_messages.lua new file mode 100644 index 0000000..834e110 --- /dev/null +++ b/lua/cfc_chat_transit/server/remote_messages.lua @@ -0,0 +1,58 @@ +local IsValid +IsValid = _G.IsValid +local AddNetworkString +AddNetworkString = util.AddNetworkString +local Start, Receive, ReadBool, WriteColor, WriteString, Send +do + local _obj_0 = net + Start, Receive, ReadBool, WriteColor, WriteString, Send = _obj_0.Start, _obj_0.Receive, _obj_0.ReadBool, _obj_0.WriteColor, _obj_0.WriteString, _obj_0.Send +end +local ToColor +ToColor = string.ToColor +AddNetworkString("CFC_ChatTransit_RemoteMessagePreference") +AddNetworkString("CFC_ChatTransit_RemoteMessageReceive") +local recipients = RecipientFilter() +local adminRecipients = RecipientFilter() +local shouldTransmit = CreateConVar("cfc_chat_transit_should_transmit_remote", 1, FCVAR_ARCHIVE, "Should transmit remote messages", 0, 1) +local adminOnly = CreateConVar("cfc_chat_transit_transmit_admin_only", 1, FCVAR_ARCHIVE, "Should only transmit to Admins?", 0, 1) +Receive("CFC_ChatTransit_RemoteMessagePreference", function(_, ply) + local shouldReceive = ReadBool() + if shouldReceive then + recipients:AddPlayer(ply) + if adminOnly:GetBool() and ply:IsAdmin() then + adminRecipients:AddPlayer(ply) + end + return + end + recipients:RemovePlayer(ply) + return adminRecipients:RemovePlayer(ply) +end) +local broadcastMessage +broadcastMessage = function(ply, cmd, args, argStr) + if not (shouldTransmit:GetBool()) then + return + end + if IsValid(ply) then + return + end + local author = rawget(args, 1) + local authorColor = rawget(args, 2) + local message = rawget(args, 3) + if not (author) then + return + end + if not (authorColor) then + return + end + if not (message) then + return + end + authorColor = ToColor(authorColor) + local sendingTo = adminOnly:GetBool() and adminRecipients or recipients + Start("CFC_ChatTransit_RemoteMessageReceive") + WriteString(author) + WriteColor(authorColor) + WriteString(message) + return Send(sendingTo) +end +return concommand.Add("chat_transit", broadcastMessage) diff --git a/moon/cfc_chat_transit/server/avatar_service.moon b/moon/cfc_chat_transit/server/avatar_service.moon index a524aca..a55cafb 100644 --- a/moon/cfc_chat_transit/server/avatar_service.moon +++ b/moon/cfc_chat_transit/server/avatar_service.moon @@ -7,7 +7,7 @@ class AvatarService new: (logger) => @logger = logger\scope "AvatarService" - @outlinerUrl = "http://#{avatarServiceAddress\GetString!}/outline" + @outlinerUrl = "#{avatarServiceAddress\GetString!}/outline" @processedIds = {} getAvatar: (steamID64) => @@ -18,11 +18,11 @@ class AvatarService processAvatar: (avatarUrl, outlineColor, steamID64) => body = TableToJSON { :avatarUrl, :outlineColor, steamID: steamID64 } - @logger\info "Sending data to outliner: ", body + @logger\debug "Sending data to outliner: ", body failed = @logger\error success = (code, body) -> - @logger\info "Avatar request succeeded with code: #{code} | Body: #{body}" + @logger\debug "Avatar request succeeded with code: #{code} | Body: #{body}" @processedIds[steamID64] = true HTTP @@ -34,7 +34,7 @@ class AvatarService type: "application/json" outlineAvatar: (ply, data) => - @logger\info "Received request to outline avatar for ply: #{ply\Nick!}" + @logger\debug "Received request to outline avatar for ply: #{ply\Nick!}" avatar = data.response.players[1].avatarfull outlineColor = ChatTransit\GetTeamColor ply\Team! steamID64 = ply\SteamID64! diff --git a/moon/cfc_chat_transit/server/init.moon b/moon/cfc_chat_transit/server/init.moon index 8e73cb8..f5f501f 100644 --- a/moon/cfc_chat_transit/server/init.moon +++ b/moon/cfc_chat_transit/server/init.moon @@ -16,8 +16,8 @@ loadHook = "ChatTransit_WebsocketLoad" hook.Add "Think", loadHook, -> hook.Remove "Think", loadHook - ChatTransit.WebSocket = GWSockets.createWebSocket "ws://#{relayHost\GetString!}/relay" - ChatTransit.Realm = CreateConVar "cfc_realm", "", FCVAR_NONE, "CFC Realm Name" + ChatTransit.WebSocket = GWSockets.createWebSocket "wss://#{relayHost\GetString!}/relay", false + ChatTransit.Realm = CreateConVar "cfc_realm", "unknown", FCVAR_REPLICATED + FCVAR_ARCHIVE, "The Realm Name" with ChatTransit.WebSocket .reconnectTimerName = "CFC_ChatTransit_WebsocketReconnect" @@ -41,7 +41,7 @@ hook.Add "Think", loadHook, -> return nil ChatTransit.Send = (data) => - logger\info "Sending '#{data.Type}'" + logger\debug "Sending '#{data.Type}'" steamID64 = data.Data.SteamId data.Data.Avatar or= ChatTransit.AvatarService\getAvatar steamID64 diff --git a/moon/cfc_chat_transit/server/modules/voice.moon b/moon/cfc_chat_transit/server/modules/voice.moon new file mode 100644 index 0000000..538de2b --- /dev/null +++ b/moon/cfc_chat_transit/server/modules/voice.moon @@ -0,0 +1,30 @@ +import IsValid from _G +import guard from ChatTransit + +local proxVoice + +proxVoiceEnabled = -> + proxVoice or= GetConVar "force_proximity_voice" + return false unless proxVoice + return proxVoice\GetBool! + +ChatTransit.ReceiveVoiceTranscript = (steamID64, data) => + return if proxVoiceEnabled! + + ply = player.GetBySteamID64 steamID64 + return unless IsValid ply + + return if ply.ulx_gagged + return if ply\GetInfoNum("proximity_voice_enabled", 0) ~= 0 + + shouldRelay = hook.Run "CFC_ChatTransit_ShouldRelayTranscript", ply, transcript + return if shouldRelay == false + + @Send + Type: "voice_transcript" + Data: + Type: "voice_transcript" + Content: data + SteamName: ply\Nick! + SteamId: ply\SteamID64! + IrisId: "none" diff --git a/web/avatar_service/Dockerfile.service b/web/avatar_service/Dockerfile similarity index 70% rename from web/avatar_service/Dockerfile.service rename to web/avatar_service/Dockerfile index 98c0283..21f33e7 100644 --- a/web/avatar_service/Dockerfile.service +++ b/web/avatar_service/Dockerfile @@ -1,13 +1,14 @@ FROM python:3.9.0 -RUN mkdir /avatars -RUN mkdir /avatar-service -WORKDIR /avatar-service -COPY . /avatar-service - RUN pip install -U --upgrade pip + +RUN mkdir /avatars +WORKDIR /avatar-service + +COPY requirements.txt . RUN pip install --upgrade -r ./requirements.txt +COPY . . + COPY entrypoint.sh /usr/bin/ -RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] diff --git a/web/avatar_service/docker-compose.yml b/web/avatar_service/docker-compose.yml index b090d78..b4e9fa4 100644 --- a/web/avatar_service/docker-compose.yml +++ b/web/avatar_service/docker-compose.yml @@ -12,7 +12,7 @@ services: service: build: context: . - dockerfile: Dockerfile.service + dockerfile: Dockerfile command: flask run --host 0.0.0.0 --port 8080 container_name: "${REALM}_chat_transit_avatar_service" ports: @@ -22,5 +22,4 @@ services: env_file: - .env volumes: - - ./:/build/src - "$AVATARS_DIR:/avatars:z" diff --git a/web/avatar_service/entrypoint.sh b/web/avatar_service/entrypoint.sh old mode 100644 new mode 100755 diff --git a/web/discord_relay/Dockerfile b/web/discord_relay/Dockerfile index ebdd794..9818551 100644 --- a/web/discord_relay/Dockerfile +++ b/web/discord_relay/Dockerfile @@ -4,6 +4,10 @@ ENV GOPATH=/build ENV GOBIN=/usr/local/go/bin WORKDIR $GOPATH/src +COPY go.mod . +COPY go.sum . +RUN go mod download + COPY . . ARG BRANCH=HEAD diff --git a/web/discord_relay/docker-compose.yml b/web/discord_relay/docker-compose.yml index 9315fc6..d118c1b 100644 --- a/web/discord_relay/docker-compose.yml +++ b/web/discord_relay/docker-compose.yml @@ -9,3 +9,4 @@ services: - .env volumes: - ./:/build/src + restart: always diff --git a/web/discord_relay/go.mod b/web/discord_relay/go.mod index 570fd5b..d505012 100644 --- a/web/discord_relay/go.mod +++ b/web/discord_relay/go.mod @@ -3,7 +3,8 @@ module github.com/cfc-servers/cfc_chat_transit go 1.15 require ( - github.com/bwmarrin/discordgo v0.23.2 + github.com/bwmarrin/discordgo v0.25.0 github.com/getsentry/sentry-go v0.9.0 github.com/gorilla/websocket v1.4.2 + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect ) diff --git a/web/discord_relay/go.sum b/web/discord_relay/go.sum index c289bc0..adb968c 100644 --- a/web/discord_relay/go.sum +++ b/web/discord_relay/go.sum @@ -9,6 +9,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/bwmarrin/discordgo v0.25.0 h1:NXhdfHRNxtwso6FPdzW2i3uBvvU7UIQTghmV2T4nqAs= +github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -96,6 +98,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -141,6 +145,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -150,6 +156,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -159,8 +166,12 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/web/discord_relay/queue.go b/web/discord_relay/queue.go index bb8f48b..ccf0280 100644 --- a/web/discord_relay/queue.go +++ b/web/discord_relay/queue.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/bwmarrin/discordgo" + "github.com/cfc-servers/cfc_chat_transit/voice" ) var discord *discordgo.Session @@ -30,11 +31,25 @@ type EventData struct { PlayerCountCurrent float32 } -var MessageQueue = make(chan []byte, 100) +type VoiceMessageOperation struct { + Message string + MessageId string + SteamId string + SteamName string + Avatar string + IsFinal bool +} + +var MessageQueue = make(chan []byte, 10000) var WebhookId string = os.Getenv("WEBHOOK_ID") var WebhookSecret string = os.Getenv("WEBHOOK_SECRET") +var VoiceWebhookId string = os.Getenv("VOICE_WEBHOOK_ID") +var VoiceWebhookSecret string = os.Getenv("VOICE_WEBHOOK_SECRET") + +var DiscordToken string = os.Getenv("DISCORD_TOKEN") + const urlRegexString = `https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)` const ( @@ -43,9 +58,11 @@ const ( EMOJI_HALTED = "<:halted:398133588010336259>" EMOJI_BUILD = "<:build:933512140395012107>" EMOJI_PVP = "<:bk:812130062379515906>" + EMOJI_PLAY = "<:playbuttonsmaller:1017716044485382154>" EMOJI_MAP = "🗺️" EMOJI_CONNECT = "📡" EMOJI_ULX = "⌨️" + EMOJI_VOICE = "🗣️" COLOR_RED = 0xE7373E COLOR_GREEN = 0x37E73E @@ -89,7 +106,7 @@ func sendMessage(discord *discordgo.Session, message EventStruct) { } } -func sendEvent(discord *discordgo.Session, event EventStruct, eventText string, color int, emoji string) { +func sendEvent(discord *discordgo.Session, event EventStruct, eventText string, color int, emoji string) *discordgo.Message { params := &discordgo.WebhookParams{ AllowedMentions: &discordgo.MessageAllowedMentions{ Parse: []discordgo.AllowedMentionType{}, @@ -104,11 +121,13 @@ func sendEvent(discord *discordgo.Session, event EventStruct, eventText string, }, } - _, err := discord.WebhookExecute(WebhookId, WebhookSecret, true, params) + message, err := discord.WebhookExecute(WebhookId, WebhookSecret, true, params) if err != nil { log.Println(err) } + + return message } func sendConnectMessage(discord *discordgo.Session, event EventStruct) { @@ -170,10 +189,88 @@ func sendPvpStatusChange(discord *discordgo.Session, event EventStruct) { sendEvent(discord, event, event.Data.Content, color, emoji) } -func queueGroomer() { - discord, err := discordgo.New("") +func sendVoiceText(discord *discordgo.Session, data *voice.Session) string { + transcript := data.Message + steamName := data.SteamName + avatar := data.Avatar + // fileName := data.FileName + messageId := data.MessageId + // isFinal := data.Finished - log.Println(WebhookId, WebhookSecret) + // var voiceLink string + // if len(fileName) > 0 { + // voiceLink = fmt.Sprintf("https://larynx.cfcservers.org/%v.ogg", fileName) + // } + + // var description string + + // if isFinal && len(voiceLink) > 0 { + // description = fmt.Sprintf("%v [%v](%v) %v", EMOJI_VOICE, EMOJI_PLAY, voiceLink, transcript) + // } else { + // description = fmt.Sprintf("%v %v", EMOJI_VOICE, transcript) + // } + + description := fmt.Sprintf("%v %v", EMOJI_VOICE, transcript) + + embeds := []*discordgo.MessageEmbed{ + { + Description: description, + Color: COLOR_BLUE, + }, + } + + if len(messageId) == 0 { + log.Println("Creating new message for voice") + params := &discordgo.WebhookParams{ + AllowedMentions: &discordgo.MessageAllowedMentions{ + Parse: []discordgo.AllowedMentionType{}, + }, + Username: steamName, + AvatarURL: avatar, + Embeds: embeds, + } + + message, err := discord.WebhookExecute(VoiceWebhookId, VoiceWebhookSecret, true, params) + + if err != nil { + log.Println("Error sending webhook message create") + log.Println(err) + return "" + } + + return message.ID + } else { + log.Println("Updating existing message for voice") + params := &discordgo.WebhookEdit{ + Embeds: embeds, + } + + log.Println(messageId) + log.Println(params.Embeds[0].Description) + + message, err := discord.WebhookMessageEdit(VoiceWebhookId, VoiceWebhookSecret, messageId, params) + if err != nil { + log.Println("Error sending webhook message edit") + log.Println(err) + } + + return message.ID + } +} + +func processVoiceText(queueVoiceText func(string, string, string, string), event EventStruct) { + steamId := event.Data.SteamId + steamName := event.Data.SteamName + avatar := event.Data.Avatar + data := event.Data.Content + log.Println(data) + + queueVoiceText(steamId, steamName, avatar, data) +} + +func queueGroomer() { + discord, err := discordgo.New(DiscordToken) + voiceManager := voice.NewManager(discord, sendVoiceText) if err != nil { log.Fatal("error connecting:", err) @@ -211,6 +308,9 @@ func queueGroomer() { sendUlxAction(discord, message) case "pvp_status_change": sendPvpStatusChange(discord, message) + case "voice_transcript": + processVoiceText(voiceManager.ReceiveVoiceTranscript, message) } + } } diff --git a/web/discord_relay/voice/voice.go b/web/discord_relay/voice/voice.go new file mode 100644 index 0000000..bea7c1f --- /dev/null +++ b/web/discord_relay/voice/voice.go @@ -0,0 +1,166 @@ +package voice + +import ( + "encoding/json" + "github.com/bwmarrin/discordgo" + "log" + "sync" + "time" +) + +type Session struct { + SteamId string + SteamName string + Message string + MessageId string + FileName string + Avatar string + Finished bool +} + +type Operation struct { + Session *Session +} + +type Manager struct { + Sessions map[string]*Session + Operations []*Operation + discord *discordgo.Session + opmutex *sync.Mutex + sendMessage func(*discordgo.Session, *Session) string +} + +// MessageData The "Content" key of the Event that comes into the Main package +type MessageData struct { + Message string `json:"transcript"` + FileName string `json:"file_name"` + SessionID string `json:"session_id"` + IsFinal bool `json:"is_final"` +} + +func (v *Manager) runSendQueue() { + bucket := v.discord.Ratelimiter.GetBucket("voiceTranscriptions") + + wait := func() { + time.Sleep(time.Millisecond * 100) + } + + for { + wait() + + // This should be a blocking call + // Unblocks when bucket has bandwidth + v.discord.Ratelimiter.LockBucketObject(bucket).Unlock() + + if len(v.Operations) == 0 { + wait() + continue + } + + v.opmutex.Lock() + + firstOperation := v.Operations[0] + log.Printf("Processing message for session %v", firstOperation.Session.FileName) + v.Operations = v.Operations[1:] + + session := firstOperation.Session + description := session.Message + + if len(description) == 0 { + log.Printf("No description for session %v", session.FileName) + // What is this? Why are you showing this to me + v.opmutex.Unlock() + continue + } + + messageId := v.sendMessage(v.discord, session) + + if len(messageId) == 0 { + log.Println("Received no message ID from sendMessage") + // Failed to send/update message, send to back of queue + v.Operations = append(v.Operations, firstOperation) + v.opmutex.Unlock() + continue + } else { + session.MessageId = messageId + } + + v.opmutex.Unlock() + } +} + +func (v *Manager) ReceiveVoiceTranscript(steamId string, steamName string, avatar string, data string) { + messageData := MessageData{} + err := json.Unmarshal([]byte(data), &messageData) + if err != nil { + log.Printf("Error parsing voice message: %v", err) + return + } + + newMessage := messageData.Message + isFinal := messageData.IsFinal + fileName := messageData.FileName + sessionId := messageData.SessionID + + session, ok := v.Sessions[sessionId] + + if !ok { + session = &Session{ + SteamId: steamId, + SteamName: steamName, + Message: newMessage, + MessageId: "", + Finished: isFinal, + Avatar: avatar, + FileName: fileName, + } + + v.Sessions[sessionId] = session + } else { + // Nothing changed - we don't need to queue an Operation for this + if !isFinal && session.Message == newMessage { + return + } + + session.Message = newMessage + session.Finished = isFinal + session.FileName = fileName + } + + if isFinal { + // Final message for this transcription, we can stop tracking it + // TODO: do I use delete here? + v.Sessions[sessionId] = nil + } + + v.opmutex.Lock() + defer v.opmutex.Unlock() + + if ok { + // Session already existed for sessionID + // Do we already have a pending Operation? + for _, operation := range v.Operations { + if operation.Session == session { + // If so, we don't need to add another operation + return + } + } + } + + v.Operations = append(v.Operations, &Operation{ + Session: session, + }) +} + +func NewManager(discord *discordgo.Session, sendMessage func(*discordgo.Session, *Session) string) *Manager { + manager := &Manager{ + Sessions: make(map[string]*Session), + Operations: make([]*Operation, 0), + discord: discord, + opmutex: &sync.Mutex{}, + sendMessage: sendMessage, + } + + go manager.runSendQueue() + return manager +}