From 1111203a31129a421549c7930b60e3ebb02ca1d3 Mon Sep 17 00:00:00 2001 From: Brandon Sturgeon Date: Wed, 20 Sep 2023 20:28:46 -0700 Subject: [PATCH] Add multirealm support (#45) --- README.md | 26 +++++++++++++ .../client/receive_remote_message.moon | 21 +++++----- .../server/avatar_service.moon | 35 +++++++++++------ .../server/remote_messages.moon | 23 +++++------ web/avatar_service/.env_example | 4 ++ web/avatar_service/avatars.conf | 3 +- web/avatar_service/docker-compose.yml | 2 +- web/avatar_service/service.py | 3 +- web/discord_relay/.env_example | 3 ++ web/discord_relay/docker-compose.yml | 2 +- web/discord_relay/queue.go | 34 +++++++++------- web/discord_relay/webhook/webhook.go | 39 +++++++++++++++++++ 12 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 web/avatar_service/.env_example create mode 100644 web/discord_relay/.env_example create mode 100644 web/discord_relay/webhook/webhook.go diff --git a/README.md b/README.md index 324bbf5..0086cd9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ # cfc_chat_transit Paving paths and establishing tunnels + +## Convars + +### Server +- **`cfc_avatar_service_address`** + - The domain (`avatar_api.mydomain.com`) of the Avatar Service API + +- **`cfc_avatar_service_image_address`** + - The domain (`avatars.mydomain.com`) that the Avatar images are actually served from + +- **`cfc_relay_host`** + - The domain (`relay.mydomain.com`) of the `discord_relay` service + +- `cfc_realm` + - The Realm (`cfc3` / `cfcttt` / `darkrp`) of the server that is running the addon + +- **`cfc_chat_transit_should_transmit_remote`** + - Whether or not to send Discord messages to players + +- **`cfc_chat_transit_transmit_admin_only`** + - Whether or not to send Discord messages to only Admin+ + + +### Client +- **`cfc_chat_transit_remote_messages`** + - Whether or not Discord messages should appear in Chat diff --git a/moon/cfc_chat_transit/client/receive_remote_message.moon b/moon/cfc_chat_transit/client/receive_remote_message.moon index fbf59d0..06d1fe6 100644 --- a/moon/cfc_chat_transit/client/receive_remote_message.moon +++ b/moon/cfc_chat_transit/client/receive_remote_message.moon @@ -1,18 +1,15 @@ -import Start, Receive, ReadBool, ReadColor, ReadString, WriteBool, SendToServer from net -import AddToolCategory, AddToolMenuOption from spawnmenu - shouldReceiveRemoteMessages = CreateConVar "cfc_chat_transit_remote_messages", 1, FCVAR_ARCHIVE, "Should receive remote messges in chat", 0, 1 colors = white: Color 255, 255, 255 blurple: Color 142, 163, 247 -Receive "CFC_ChatTransit_RemoteMessageReceive", -> +net.Receive "CFC_ChatTransit_RemoteMessageReceive", -> return unless shouldReceiveRemoteMessages\GetBool! - author = ReadString! - authorColor = ReadColor! - message = ReadString! + author = net.ReadString! + authorColor = net.ReadColor! + message = net.ReadString! return unless author return unless authorColor @@ -30,9 +27,9 @@ Receive "CFC_ChatTransit_RemoteMessageReceive", -> chat.AddText unpack addTextParams alertPreference = (val) -> - Start "CFC_ChatTransit_RemoteMessagePreference" - WriteBool val - SendToServer! + net.Start "CFC_ChatTransit_RemoteMessagePreference" + net.WriteBool val + net.SendToServer! initHookName = "CFC_ChatTransit_AlertRemoteMessagePreference" @@ -50,8 +47,8 @@ populatePanel = (panel) -> .OnChange = (_, val) -> alertPreference val hook.Add "AddToolMenuCategories", "CFC_ChatTransit_MenuCategory", -> - AddToolCategory "Options", "CFC", "CFC" + spawnmenu.AddToolCategory "Options", "CFC", "CFC" hook.Add "PopulateToolMenu", "CFC_ChatTransit_MenuOption", -> - AddToolMenuOption "Options", "CFC", "should_receive_remote_messages", "Remote Messages", "", "", (panel) -> + spawnmenu.AddToolMenuOption "Options", "CFC", "should_receive_remote_messages", "Remote Messages", "", "", (panel) -> populatePanel panel diff --git a/moon/cfc_chat_transit/server/avatar_service.moon b/moon/cfc_chat_transit/server/avatar_service.moon index a55cafb..7f14c51 100644 --- a/moon/cfc_chat_transit/server/avatar_service.moon +++ b/moon/cfc_chat_transit/server/avatar_service.moon @@ -1,29 +1,33 @@ import TableToJSON from util -HTTP = HTTP -avatarServiceAddress = CreateConVar "cfc_avatar_service_address", "", FCVAR_ARCHIVE + FCVAR_PROTECTED +avatarServiceAPIAddress = CreateConVar "cfc_avatar_service_address", "", FCVAR_ARCHIVE + FCVAR_PROTECTED +avatarServiceImageAddress = CreateConVar "cfc_avatar_service_image_address", "", FCVAR_ARCHIVE + FCVAR_PROTECTED class AvatarService - new: (logger) => @logger = logger\scope "AvatarService" - @outlinerUrl = "#{avatarServiceAddress\GetString!}/outline" - @processedIds = {} + @outlinerUrl = "#{avatarServiceAPIAddress\GetString!}/outline" + @processedIDs = {} getAvatar: (steamID64) => - url = steamID64 and "https://avatarservice.cfcservers.org/avatars/#{steamID64}.png" or nil - url and= "#{url}?processed=true" if @processedIds[steamID64] + imageAddress = avatarServiceImageAddress\GetString! + realm = ChatTransit.Realm\GetString! + baseURL = "https://#{imageAddress}/avatars/#{realm}" + + url = steamID64 and "#{baseURL}/#{steamID64}.png" or nil + url and= "#{url}?processed=true" if @processedIDs[steamID64] return url processAvatar: (avatarUrl, outlineColor, steamID64) => - body = TableToJSON { :avatarUrl, :outlineColor, steamID: steamID64 } + realm = ChatTransit.Realm\GetString! + body = TableToJSON { :avatarUrl, :outlineColor, :realm, steamID: steamID64 } @logger\debug "Sending data to outliner: ", body failed = @logger\error success = (code, body) -> @logger\debug "Avatar request succeeded with code: #{code} | Body: #{body}" - @processedIds[steamID64] = true + @processedIDs[steamID64] = true HTTP :success @@ -48,7 +52,16 @@ hook.Add "CFC_SteamLookup_SuccessfulPlayerData", "CFC_ChatTransit_AvatarService" return unless dataName == "PlayerSummary" return unless data - success, err = pcall -> ChatTransit.AvatarService\outlineAvatar ply, data - ErrorNoHaltWithStack err, dataName, ply unless success + ProtectedCall -> ChatTransit.AvatarService\outlineAvatar ply, data + + return nil + +hook.Add "PlayerDisconnected", "CFC_ChatTransit_AvatarServiceReset", (ply) -> + steamID64 = ply\SteamID64! + if not steamID64 + ErrorNoHalt "[ChatTransit] Failed to get player's SteamID64 in PlayerDisconnected" + return + + AvatarService.processedIDs[steamID64] = nil return nil diff --git a/moon/cfc_chat_transit/server/remote_messages.moon b/moon/cfc_chat_transit/server/remote_messages.moon index 933d45e..5bc9b81 100644 --- a/moon/cfc_chat_transit/server/remote_messages.moon +++ b/moon/cfc_chat_transit/server/remote_messages.moon @@ -1,10 +1,7 @@ import IsValid from _G -import AddNetworkString from util -import Start, Receive, ReadBool, WriteColor, WriteString, Send from net -import ToColor from string -AddNetworkString "CFC_ChatTransit_RemoteMessagePreference" -AddNetworkString "CFC_ChatTransit_RemoteMessageReceive" +util.AddNetworkString "CFC_ChatTransit_RemoteMessagePreference" +util.AddNetworkString "CFC_ChatTransit_RemoteMessageReceive" recipients = RecipientFilter! adminRecipients = RecipientFilter! @@ -13,8 +10,8 @@ shouldTransmit = CreateConVar "cfc_chat_transit_should_transmit_remote", 1, FCVA adminOnly = CreateConVar "cfc_chat_transit_transmit_admin_only", 1, FCVAR_ARCHIVE, "Should only transmit to Admins?", 0, 1 -- TODO: Handle rank changes (to/from Admin) -Receive "CFC_ChatTransit_RemoteMessagePreference", (_, ply) -> - shouldReceive = ReadBool! +net.Receive "CFC_ChatTransit_RemoteMessagePreference", (_, ply) -> + shouldReceive = net.ReadBool! if shouldReceive recipients\AddPlayer ply @@ -40,13 +37,13 @@ broadcastMessage = (ply, cmd, args, argStr) -> return unless authorColor return unless message - authorColor = ToColor authorColor + authorColor = string.ToColor authorColor sendingTo = adminOnly\GetBool! and adminRecipients or recipients - Start "CFC_ChatTransit_RemoteMessageReceive" - WriteString author - WriteColor authorColor - WriteString message - Send sendingTo + net.Start "CFC_ChatTransit_RemoteMessageReceive" + net.WriteString author + net.WriteColor authorColor + net.WriteString message + net.Send sendingTo concommand.Add "chat_transit", broadcastMessage diff --git a/web/avatar_service/.env_example b/web/avatar_service/.env_example new file mode 100644 index 0000000..8e1db3b --- /dev/null +++ b/web/avatar_service/.env_example @@ -0,0 +1,4 @@ +PORT=9101 +API_PORT=9100 +AVATARS_DIR="/some/path/to/a/dir/on/host" +AVATAR_SERVICE_URL="https://avatars.mydomain.com" diff --git a/web/avatar_service/avatars.conf b/web/avatar_service/avatars.conf index 6d862fd..3d74544 100644 --- a/web/avatar_service/avatars.conf +++ b/web/avatar_service/avatars.conf @@ -25,7 +25,8 @@ http { listen 80; location /avatars/ { - add_header 'Cache-Control' 'public, s-maxage 3600, proxy-revalidate'; + # add_header 'Cache-Control' 'public, s-maxage 3600, proxy-revalidate'; + add_header 'Cache-Control' 'no-store'; include /etc/nginx/mime.types; root /usr/share/nginx/html; autoindex on; diff --git a/web/avatar_service/docker-compose.yml b/web/avatar_service/docker-compose.yml index b4e9fa4..94a9efc 100644 --- a/web/avatar_service/docker-compose.yml +++ b/web/avatar_service/docker-compose.yml @@ -14,7 +14,7 @@ services: context: . dockerfile: Dockerfile command: flask run --host 0.0.0.0 --port 8080 - container_name: "${REALM}_chat_transit_avatar_service" + container_name: chat_transit_avatar_service ports: - "127.0.0.1:$PORT:8080" environment: diff --git a/web/avatar_service/service.py b/web/avatar_service/service.py index 7a3f0b3..782870b 100644 --- a/web/avatar_service/service.py +++ b/web/avatar_service/service.py @@ -13,9 +13,10 @@ @app.route("/outline", methods=["POST"]) def outline() -> str: content = request.json + realm = content["realm"] steam_id = content["steamID"] - avatar_path = f"/avatars/{steam_id}.png" + avatar_path = f"/avatars/{realm}/{steam_id}.png" if os.path.isfile(avatar_path): os.remove(avatar_path) diff --git a/web/discord_relay/.env_example b/web/discord_relay/.env_example new file mode 100644 index 0000000..f1191c8 --- /dev/null +++ b/web/discord_relay/.env_example @@ -0,0 +1,3 @@ +PORT=9103 +SENTRY_DSN="https://dc69197dffc94fcf8cc025c5f00ead45@o380324.ingest.sentry.io/5636934" +DISCORD_TOKEN="LQx6IeNs9mtFICwrrB1xqMB2Fgl5-fvf" diff --git a/web/discord_relay/docker-compose.yml b/web/discord_relay/docker-compose.yml index d118c1b..65f9c3c 100644 --- a/web/discord_relay/docker-compose.yml +++ b/web/discord_relay/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: web: build: . - container_name: "${REALM}_chat_transit_discord_relay" + container_name: "chat_transit_discord_relay" ports: - "127.0.0.1:$PORT:8080" env_file: diff --git a/web/discord_relay/queue.go b/web/discord_relay/queue.go index 4db65b9..75a6a39 100644 --- a/web/discord_relay/queue.go +++ b/web/discord_relay/queue.go @@ -10,6 +10,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/cfc-servers/cfc_chat_transit/voice" + "github.com/cfc-servers/cfc_chat_transit/webhook" ) var discord *discordgo.Session @@ -42,9 +43,6 @@ type VoiceMessageOperation struct { 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") @@ -53,16 +51,16 @@ 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 ( - EMOJI_JOIN = "<:green_cross_cir:654105378933571594>" - EMOJI_LEAVE = "<:circle_red:855605697978957854>" - EMOJI_HALTED = "<:halted:398133588010336259>" - EMOJI_BUILD = "<:build:933512140395012107>" - EMOJI_PVP = "<:bk:812130062379515906>" - EMOJI_PLAY = "<:playbuttonsmaller:1017716044485382154>" - EMOJI_MAP = "🗺️" - EMOJI_CONNECT = "📡" - EMOJI_ULX = "⌨️" - EMOJI_VOICE = "🗣️" + EMOJI_JOIN = "<:green_cross_cir:654105378933571594>" + EMOJI_LEAVE = "<:circle_red:855605697978957854>" + EMOJI_HALTED = "<:halted:398133588010336259>" + EMOJI_BUILD = "<:build:933512140395012107>" + EMOJI_PVP = "<:bk:812130062379515906>" + EMOJI_PLAY = "<:playbuttonsmaller:1017716044485382154>" + EMOJI_MAP = "🗺️" + EMOJI_CONNECT = "📡" + EMOJI_ULX = "⌨️" + EMOJI_VOICE = "🗣️" EMOJI_ROUND_MODIFIER = "🔵" COLOR_RED = 0xE7373E @@ -101,7 +99,10 @@ func sendMessage(discord *discordgo.Session, message EventStruct) { AvatarURL: message.Data.Avatar, } - _, err := discord.WebhookExecute(WebhookId, WebhookSecret, true, params) + realm := message.Realm + webhookInfo := webhook.Get(realm) + + _, err := discord.WebhookExecute(webhookInfo.ID, webhookInfo.Secret, true, params) if err != nil { log.Println("WebhookExecute errored: ", err) } @@ -122,7 +123,10 @@ func sendEvent(discord *discordgo.Session, event EventStruct, eventText string, }, } - message, err := discord.WebhookExecute(WebhookId, WebhookSecret, true, params) + realm := event.Realm + webhookInfo := webhook.Get(realm) + + message, err := discord.WebhookExecute(webhookInfo.ID, webhookInfo.Secret, true, params) if err != nil { log.Println(err) diff --git a/web/discord_relay/webhook/webhook.go b/web/discord_relay/webhook/webhook.go new file mode 100644 index 0000000..d0d23c9 --- /dev/null +++ b/web/discord_relay/webhook/webhook.go @@ -0,0 +1,39 @@ +package webhook + +import ( + "fmt" + "os" + "sync" +) + +type WebhookInfo struct { + ID string + Secret string +} + +var cache = make(map[string]*WebhookInfo) +var mu sync.Mutex + +func Get(realm string) *WebhookInfo { + mu.Lock() + defer mu.Unlock() + + if info, exists := cache[realm]; exists { + return info + } + + // Expects cfc3_WEBHOOK_ID / cfc3_WEBHOOK_SECRET + idEnv := fmt.Sprintf("%s_WEBHOOK_ID", realm) + secretEnv := fmt.Sprintf("%s_WEBHOOK_SECRET", realm) + + id := os.Getenv(idEnv) + secret := os.Getenv(secretEnv) + + info := &WebhookInfo{ + ID: id, + Secret: secret, + } + + cache[realm] = info + return info +}