Skip to content

Commit b331b25

Browse files
committed
Rework chat link to use webhook
This allows the fctrl discord bot to "impersonate" in-game usernames as their nicknames, instead of simply prefixing it to the message. Update Cargo dependencies
1 parent 538f6c7 commit b331b25

File tree

8 files changed

+492
-178
lines changed

8 files changed

+492
-178
lines changed

Cargo.lock

Lines changed: 411 additions & 136 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,46 @@ async_zip = { version = "0.0.17", features = [ "full" ] }
1212
base64 = "0.22.1"
1313
bincode = "1.3"
1414
bytes = "1.8.0"
15-
chrono = { version = "0.4.38", features = [ "serde" ] }
15+
chrono = { version = "0.4.39", features = [ "serde" ] }
1616
derive_more = { version = "1.0", features = [ "full" ] }
17-
env_logger = "0.11.5"
17+
env_logger = "0.11.6"
1818
factorio-file-parser = { git = "https://github.com/circlesabound/factorio-file-parser", rev = "6a4c062" }
1919
futures = "0.3.31"
2020
futures-util = "0.3.31"
21-
http = "1.1.0"
21+
http = "1.2.0"
2222
lazy_static = "1.5.0"
2323
log = "0.4.22"
2424
nix = { version = "0.29", features = [ "process", "signal" ] }
2525
rand = "0.8.5"
2626
rcon = { version = "0.6", features = [ "rt-tokio" ] }
27-
regex = "1.11.0"
28-
reqwest = { version = "0.12.8", features = [ "json" ] }
29-
rocksdb = "0.22"
27+
regex = "1.11.1"
28+
reqwest = { version = "0.12.12", features = [ "json" ] }
29+
rocksdb = "0.23"
3030
rocket = { version = "0.5.1", features = [ "json" ] }
31-
serde = { version = "1.0.210", features = [ "derive" ] }
32-
serde_json = "1.0.132"
33-
serenity = { version = "0.12.2", default-features = false, features = [ "client", "gateway", "rustls_backend", "model", "cache" ] }
31+
serde = { version = "1.0.217", features = [ "derive" ] }
32+
serde_json = "1.0.134"
33+
serenity = { version = "0.12.4", default-features = false, features = [ "client", "gateway", "rustls_backend", "model", "cache" ] }
3434
stream-cancel = "0.8.2"
3535
strum = "0.26.3"
3636
strum_macros = "0.26.4"
37-
sysinfo = "0.32.0"
38-
tar = "0.4.42"
39-
tokio = { version = "1.40.0", features = [ "full" ] }
40-
tokio-stream = { version = "0.1.16", features = [ "sync" ] }
41-
tokio-tungstenite = { version = "0.24", features = [ "native-tls", "url" ] }
42-
tokio-util = "0.7.12"
37+
sysinfo = "0.33.1"
38+
tar = "0.4.43"
39+
tokio = { version = "1.42.0", features = [ "full" ] }
40+
tokio-stream = { version = "0.1.17", features = [ "sync" ] }
41+
tokio-tungstenite = { version = "0.26.1", features = [ "native-tls", "url" ] }
42+
tokio-util = "0.7.13"
4343
toml = "0.8.19"
4444
unicode-xid = "0.2.6"
45-
url = "2.5.2"
45+
url = "2.5.4"
4646
urlencoding = "2.1.3"
4747
uuid = { version = "1.11.0", features = [ "serde", "v4" ] }
4848
xz2 = "0.1.7"
4949

5050
[build-dependencies]
51-
vergen-gitcl = { version = "1.0", features = [ "build" ] }
51+
vergen-gitcl = { version = "1.0.2", features = [ "build" ] }
5252

5353
[dev-dependencies]
54-
serial_test = "3.1.1"
54+
serial_test = "3.2.0"
5555

5656
[target.'cfg(not(windows))'.dependencies]
5757
openssl-sys = { version = "0.9.104", features = [ "vendored" ] }

src/agent/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ impl AgentController {
196196
debug!("Sending streaming message: {}", json);
197197
AgentController::_send_message(
198198
Arc::clone(&ws_tx_clone),
199-
Message::Text(json),
199+
Message::Text(json.into()),
200200
)
201201
.await;
202202
}
@@ -477,7 +477,7 @@ impl AgentController {
477477
}
478478
Ok(json) => {
479479
debug!("Sending ack: {}", json);
480-
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json)).await;
480+
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json.into())).await;
481481
}
482482
}
483483
}
@@ -496,7 +496,7 @@ impl AgentController {
496496
}
497497
Ok(json) => {
498498
debug!("Sending reply: {}", json);
499-
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json)).await;
499+
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json.into())).await;
500500
}
501501
}
502502
}
@@ -515,7 +515,7 @@ impl AgentController {
515515
}
516516
Ok(json) => {
517517
debug!("Sending reply_success: {}", json);
518-
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json)).await;
518+
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json.into())).await;
519519
}
520520
}
521521
}
@@ -534,7 +534,7 @@ impl AgentController {
534534
}
535535
Ok(json) => {
536536
debug!("Sending reply_failed: {}", json);
537-
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json)).await;
537+
AgentController::_send_message(Arc::clone(&self.ws_tx), Message::Text(json.into())).await;
538538
}
539539
}
540540
}

src/agent/server/proc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ pub struct ProcessManager {
2525

2626
impl ProcessManager {
2727
pub fn new() -> Self {
28-
let sysinfo_refresh_specifics = RefreshKind::new()
29-
.with_cpu(CpuRefreshKind::new().with_cpu_usage())
30-
.with_memory(MemoryRefreshKind::new().with_ram());
28+
let sysinfo_refresh_specifics = RefreshKind::nothing()
29+
.with_cpu(CpuRefreshKind::nothing().with_cpu_usage())
30+
.with_memory(MemoryRefreshKind::nothing().with_ram());
3131
let sysinfo = Arc::new(RwLock::new(System::new_with_specifics(sysinfo_refresh_specifics)));
3232
let sysinfo_arc = Arc::clone(&sysinfo);
3333
tokio::spawn(async move {

src/mgmt-server/clients.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ pub async fn connect(ws_addr: url::Url, event_broker: Arc<EventBroker>) -> Resul
542542
let keep_alive_task = tokio::spawn(async move {
543543
while consecutive_missed_pings_1.load(Ordering::Acquire) < 3 {
544544
tokio::time::sleep(Duration::from_secs(15)).await;
545-
let ping = Message::Ping(b"ping".to_vec());
545+
let ping = Message::Ping(b"ping".to_vec().into());
546546
if let Err(e) = ws_write_1.lock().await.send(ping).await {
547547
error!("Failed to send ping: {:?}", e);
548548
} else {
@@ -558,7 +558,7 @@ pub async fn connect(ws_addr: url::Url, event_broker: Arc<EventBroker>) -> Resul
558558
let forward_outgoing_task = tokio::spawn(async move {
559559
pin_mut!(outgoing_stream);
560560
while let Some(outgoing_event) = outgoing_stream.next().await {
561-
let msg = Message::Text(outgoing_event.content);
561+
let msg = Message::Text(outgoing_event.content.into());
562562
if let Err(e) = ws_write_2.lock().await.send(msg).await {
563563
error!("Websocket error sending request to agent: {:?}", e);
564564
break;
@@ -574,7 +574,7 @@ pub async fn connect(ws_addr: url::Url, event_broker: Arc<EventBroker>) -> Resul
574574
Ok(msg) => {
575575
match msg {
576576
Message::Text(s) => {
577-
if let Some(event) = tag_incoming_message(s) {
577+
if let Some(event) = tag_incoming_message(s.to_string()) {
578578
event_broker.publish(event).await;
579579
}
580580
}

src/mgmt-server/discord.rs

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{collections::HashMap, time::Duration};
55
use fctrl::schema::{InternalServerState, ServerStatus};
66
use futures::{pin_mut, StreamExt};
77
use log::{error, info, warn};
8-
use serenity::all::CreateCommand;
8+
use serenity::all::{Builder, CreateCommand, CreateWebhook, ExecuteWebhook};
99
use serenity::gateway::ActivityData;
1010
use serenity::{
1111
client::{Cache, Context, EventHandler},
@@ -68,17 +68,48 @@ impl DiscordClient {
6868

6969
if let Some(chat_link_channel_id) = chat_link_channel_id {
7070
let bot_token_clone = bot_token.clone();
71+
// regular string type mpsc for non webhook messages
7172
let (chat_link_tx, mut rx) = mpsc::unbounded_channel();
73+
// for webhook messages we need extra data for "nickname impersonation"
74+
let (webhook_msg_tx, mut webhook_rx) = mpsc::unbounded_channel();
75+
76+
let http = Arc::new(Http::new(&bot_token_clone));
77+
let channel = ChannelId::new(chat_link_channel_id);
78+
79+
// we use a webhook to allow custom display nickname of sent messages
80+
// create if not exist
81+
let existing_webhooks = channel.webhooks(&http).await?;
82+
let webhook = if let Some(w) = existing_webhooks.into_iter().find(|w| w.name.as_ref().is_some_and(|n| n == "fctrl_chat_link_g2d")) {
83+
w
84+
} else {
85+
let create_webhook = CreateWebhook::new("fctrl_chat_link_g2d");
86+
create_webhook.execute(&http, channel).await?
87+
};
88+
89+
// regular non webhook message handler
90+
let http_clone = Arc::clone(&http);
7291
tokio::spawn(async move {
73-
let http = Http::new(&bot_token_clone);
74-
let channel = ChannelId::new(chat_link_channel_id);
7592
while let Some(line) = rx.recv().await {
76-
if let Err(e) = channel.say(&http, line).await {
93+
if let Err(e) = channel.say(&http_clone, line).await {
7794
error!("Couldn't send message to Discord: {:?}", e);
7895
}
7996
}
8097
});
81-
DiscordClient::create_chat_link_g2d_subscriber(chat_link_tx.clone(), event_broker)
98+
99+
// webhook message handler
100+
let http_clone = Arc::clone(&http);
101+
tokio::spawn(async move {
102+
while let Some((name, line)) = webhook_rx.recv().await {
103+
let content = ExecuteWebhook::new()
104+
.username(name)
105+
.content(line);
106+
if let Err(e) = webhook.execute(&http_clone, false, content).await {
107+
error!("Couldn't send message to Discord: {:?}", e);
108+
}
109+
}
110+
});
111+
112+
DiscordClient::create_chat_link_g2d_subscriber(chat_link_tx.clone(), webhook_msg_tx, event_broker)
82113
.await;
83114
}
84115

@@ -189,9 +220,10 @@ impl DiscordClient {
189220

190221
async fn create_chat_link_g2d_subscriber(
191222
send_msg_tx: mpsc::UnboundedSender<String>,
223+
webhook_msg_tx: mpsc::UnboundedSender<(String, String)>,
192224
event_broker: Arc<EventBroker>,
193225
) {
194-
let chat_tx = send_msg_tx.clone();
226+
let chat_tx = webhook_msg_tx;
195227
let join_tx = send_msg_tx.clone();
196228
let leave_tx = send_msg_tx.clone();
197229
let statechange_tx = send_msg_tx;
@@ -202,10 +234,18 @@ impl DiscordClient {
202234
tokio::spawn(async move {
203235
pin_mut!(chat_sub);
204236
while let Some(event) = chat_sub.next().await {
205-
let message = event.tags.get(&TopicName::new(CHAT_TOPIC_NAME)).unwrap();
206-
if let Err(e) = chat_tx.send(message.clone()) {
207-
error!("Error sending line through mpsc channel: {:?}", e);
208-
break;
237+
let line = event.tags.get(&TopicName::new(CHAT_TOPIC_NAME)).unwrap();
238+
// assume names cannot have colon
239+
match line.split_once(": ") {
240+
Some((nick, message)) => {
241+
if let Err(e) = chat_tx.send((nick.to_string(), message.to_string())) {
242+
error!("Error sending line through mpsc channel: {:?}", e);
243+
break;
244+
}
245+
},
246+
None => {
247+
warn!("Failed to parse nick and message out of incoming chat link g2d line: {}", line);
248+
}
209249
}
210250
}
211251

@@ -290,7 +330,6 @@ impl DiscordClient {
290330
});
291331
}
292332

293-
294333
}
295334

296335
fn parse_serverstate_topic_value(states_str: impl AsRef<str>) -> Option<(InternalServerState, InternalServerState)> {

src/mgmt-server/ws.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl WebSocketServer {
125125
let outgoing_tx_clone = outgoing_tx.clone();
126126
pin_mut!(stream);
127127
let forward_fut = stream.for_each(|e| {
128-
let msg = Message::Text(e.content);
128+
let msg = Message::Text(e.content.into());
129129
let _ = outgoing_tx_clone.send(msg);
130130
future::ready(())
131131
});

src/ws-client/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ where
7171
}
7272
Some(req) => {
7373
if ws_write
74-
.send(Message::Text(serde_json::to_string(&req).unwrap()))
74+
.send(Message::Text(serde_json::to_string(&req).unwrap().into()))
7575
.await
7676
.is_err()
7777
{

0 commit comments

Comments
 (0)