Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit sending "rich" content via notify #355

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions crates/console-host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use std::time::SystemTime;
use clap::Parser;
use clap_derive::Parser;
use color_eyre::owo_colors::OwoColorize;
use moor_values::var::Objid;
use moor_values::model::Event::Notify;
use moor_values::var::{Objid, Variant};
use rustyline::config::Configurer;
use rustyline::error::ReadlineError;
use rustyline::{ColorMode, DefaultEditor, ExternalPrinter};
Expand Down Expand Up @@ -243,14 +244,13 @@ fn console_loop(
.spawn(move || loop {
match narrative_recv(client_id, &narr_sub_socket) {
Ok(ConnectionEvent::Narrative(_, msg)) => {
printer
.print(
(match msg.event() {
moor_values::model::Event::TextNotify(s) => s,
})
.to_string(),
)
.unwrap();
let Notify(msg, _content_type) = msg.event();
// TODO: Handle non text/plain content type
let msg_text = match msg.variant() {
Variant::Str(s) => s.as_str().to_string(),
_ => msg.to_literal(),
};
printer.print(msg_text).unwrap();
}
Ok(ConnectionEvent::SystemMessage(o, msg)) => {
printer
Expand Down
39 changes: 35 additions & 4 deletions crates/kernel/src/builtins/bf_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,57 @@ fn bf_noop(bf_args: &mut BfCallState<'_>) -> Result<BfRet, BfErr> {
bf_declare!(noop, bf_noop);

fn bf_notify(bf_args: &mut BfCallState<'_>) -> Result<BfRet, BfErr> {
if bf_args.args.len() != 2 {
if bf_args.args.len() < 2 || bf_args.args.len() > 3 {
return Err(BfErr::Code(E_ARGS));
}
let player = bf_args.args[0].variant();
let Variant::Obj(player) = player else {
return Err(BfErr::Code(E_TYPE));
};

// Optional 3rd argument is content-type.
let content_type = if bf_args.args.len() == 3 {
let content_type = bf_args.args[2].variant();
let Variant::Str(content_type) = content_type else {
return Err(BfErr::Code(E_TYPE));
};
Some(content_type.as_str().to_string())
} else {
None
};

// The message is the 2nd argument.
// If content-type is text/* anything (or default) this must be a string, and is validated as such
// up-front. We do this to remain compatible with existing MOO core assumptions.
// Otherwise, we pass on the Var value unmodified.
let msg = bf_args.args[1].variant();
let Variant::Str(msg) = msg else {
return Err(BfErr::Code(E_TYPE));

let must_be_str = if content_type.is_none() {
true
} else if let Some(content_type) = &content_type {
content_type.starts_with("text/")
} else {
false
};

if must_be_str {
let Variant::Str(_) = msg else {
return Err(BfErr::Code(E_TYPE));
};
}

// If player is not the calling task perms, or a caller is not a wizard, raise E_PERM.
bf_args
.task_perms()
.map_err(world_state_bf_err)?
.check_obj_owner_perms(*player)
.map_err(world_state_bf_err)?;

let event = NarrativeEvent::notify_text(bf_args.exec_state.caller(), msg.to_string());
let event = NarrativeEvent::notify(
bf_args.exec_state.caller(),
bf_args.args[1].clone(),
content_type,
);
bf_args.task_scheduler_client.notify(*player, event);

// MOO docs say this should return none, but in reality it returns 1?
Expand Down
5 changes: 4 additions & 1 deletion crates/kernel/src/tasks/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,10 @@ mod tests {
};
assert_eq!(player, SYSTEM_OBJECT);
assert_eq!(event.author(), SYSTEM_OBJECT);
assert_eq!(event.event, Event::TextNotify("12345".to_string()));
assert_eq!(
event.event,
Event::Notify(v_str("12345"), "text/plain".to_string())
);

// Also scheduler should have received a TaskSuccess message.
let (task_id, msg) = control_receiver.recv().unwrap();
Expand Down
26 changes: 19 additions & 7 deletions crates/telnet-host/src/telnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use uuid::Uuid;

use moor_values::model::{CommandError, VerbProgramError};
use moor_values::util::parse_into_words;
use moor_values::var::Objid;
use moor_values::var::{Objid, Variant};
use rpc_async_client::pubsub_client::{broadcast_recv, narrative_recv};
use rpc_async_client::rpc_client::RpcSendClient;
use rpc_common::RpcRequest::ConnectionEstablish;
Expand Down Expand Up @@ -138,9 +138,15 @@ impl TelnetConnection {
self.write.send(msg).await.with_context(|| "Unable to send message to client")?;
}
ConnectionEvent::Narrative(_author, event) => {
let msg = event.event();
let moor_values::model::Event::TextNotify(msg_text) = msg;
self.write.send(msg_text).await.with_context(|| "Unable to send message to client")?;
let moor_values::model::Event::Notify(msg, _content_type) = event.event();
// TODO: Handle non text/plain content type
let msg_text = match msg.variant() {
Variant::Str(s) => s.as_str().to_string(),
_ => {
msg.to_string()
}
};
self.write.send(msg_text.to_string()).await.with_context(|| "Unable to send message to client")?;
}
ConnectionEvent::RequestInput(_request_id) => {
bail!("RequestInput before login");
Expand Down Expand Up @@ -311,9 +317,15 @@ impl TelnetConnection {
self.write.send(msg).await.with_context(|| "Unable to send message to client")?;
}
ConnectionEvent::Narrative(_author, event) => {
let msg = event.event();
let moor_values::model::Event::TextNotify(msg_text) = msg;
self.write.send(msg_text).await.with_context(|| "Unable to send message to client")?;
let moor_values::model::Event::Notify(msg, _content_type) = event.event();
// TODO: Handle non text/plain content type
let msg_text = match msg.variant() {
Variant::Str(s) => s.as_str().to_string(),
_ => {
msg.to_string()
}
};
self.write.send(msg_text.to_string()).await.with_context(|| "Unable to send message to client")?;
}
ConnectionEvent::RequestInput(request_id) => {
// Server is requesting that the next line of input get sent through as a response to this request.
Expand Down
13 changes: 9 additions & 4 deletions crates/values/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ pub use crate::model::world_state::{WorldState, WorldStateSource};
use crate::AsByteBuffer;
use thiserror::Error;

use crate::var::Error;
use crate::var::Objid;
use crate::var::Symbol;
use crate::var::{Error, Var};

mod defset;
mod r#match;
Expand Down Expand Up @@ -167,19 +167,24 @@ pub struct NarrativeEvent {
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub enum Event {
/// The typical "something happened" descriptive event.
TextNotify(String),
/// Text +content-type
/// - "text/plain" for plain text
/// - "text/html" for HTML
/// - "text/markdown" for Markdown
/// etc.
Notify(Var, String),
// TODO: Other Event types on Session stream
// other events that might happen here would be things like (local) "object moved" or "object
// created."
}

impl NarrativeEvent {
#[must_use]
pub fn notify_text(author: Objid, event: String) -> Self {
pub fn notify(author: Objid, event: Var, content_type: Option<String>) -> Self {
Self {
timestamp: SystemTime::now(),
author,
event: Event::TextNotify(event),
event: Event::Notify(event, content_type.unwrap_or("text/plain".to_string())),
}
}

Expand Down
21 changes: 16 additions & 5 deletions crates/web-host/src/host/ws_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
// this program. If not, see <https://www.gnu.org/licenses/>.
//

use crate::host::var_as_json;
use axum::extract::ws::{Message, WebSocket};
use futures_util::stream::SplitSink;
use futures_util::{SinkExt, StreamExt};
use moor_values::model::CommandError;
use moor_values::model::{CommandError, Event};
use moor_values::var::Objid;
use rpc_async_client::pubsub_client::broadcast_recv;
use rpc_async_client::pubsub_client::narrative_recv;
Expand All @@ -25,12 +26,14 @@ use rpc_common::ConnectionEvent;
use rpc_common::{
AuthToken, ClientToken, ConnectType, RpcRequest, RpcRequestError, RpcResponse, RpcResult,
};
use serde_json::Value;
use std::net::SocketAddr;
use std::time::SystemTime;
use tmq::subscribe::Subscribe;
use tokio::select;
use tracing::{debug, error, info, trace};
use uuid::Uuid;

pub struct WebSocketConnection {
pub(crate) player: Objid,
pub(crate) peer_addr: SocketAddr,
Expand All @@ -49,7 +52,8 @@ pub struct NarrativeOutput {
#[serde(skip_serializing_if = "Option::is_none")]
system_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
message: Option<Value>,
content_type: String,
server_time: SystemTime,
}

Expand All @@ -69,6 +73,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some(connect_message.to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
},
)
Expand Down Expand Up @@ -103,17 +108,18 @@ impl WebSocketConnection {
origin_player: author.0,
system_message: Some(msg),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
}).await;
}
ConnectionEvent::Narrative(author, event) => {
let msg = event.event();
let Event::Notify(msg, content_type) = msg;
Self::emit_event(&mut ws_sender, NarrativeOutput {
origin_player: author.0,
system_message: None,
message: Some(match msg {
moor_values::model::Event::TextNotify(msg) => msg,
}),
message: Some(var_as_json(&msg)),
content_type,
server_time: event.timestamp(),
}).await;
}
Expand All @@ -125,6 +131,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some("** Disconnected **".to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
}).await;
ws_sender.close().await.expect("Unable to close connection");
Expand Down Expand Up @@ -183,6 +190,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some("I don't understand that.".to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
},
)
Expand All @@ -195,6 +203,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some("I don't know what you're talking about.".to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
},
)
Expand All @@ -207,6 +216,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some("I don't know how to do that.".to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
},
)
Expand All @@ -219,6 +229,7 @@ impl WebSocketConnection {
origin_player: self.player.0,
system_message: Some("You can't do that.".to_string()),
message: None,
content_type: "text/plain".to_string(),
server_time: SystemTime::now(),
},
)
Expand Down
Loading