use anyhow::Error; use gc_arena::{Gc, Rootable}; use piccolo::{ self, async_sequence, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, }; use telopt::{EOR_TELOPT, TERMTYPE_TELOPT}; use wasm_bindgen::JsValue; use web_sys::{console, window}; use yew::UseStateSetter; use crate::{ command_handler::execute_queue, echo_to_term_frame, id_intern::{intern_id, unintern_id}, logging::{start_deleting_logs, start_downloading_logs, start_listing_logs}, match_table::{create_match_table, match_table_add, match_table_remove}, telnet::{parse_telnet_buf, TelnetOutput}, websocket::{connect_websocket, send_message_to_mud, WebSocketId}, FrameId, GlobalLayoutCell, GlobalMemoCell, }; use self::telopt::{ handle_incoming_do, handle_incoming_dont, handle_incoming_will, handle_incoming_wont, send_subnegotiation_if_allowed, set_option_supported, MudWithMemo, Side, Telopt, NAWS_TELOPT, }; use super::{call_checking_metatable, list_match_tab, try_unwrap_frame, LuaState}; pub mod telopt; fn try_unwrap_socketid<'gc>( ctx: Context<'gc>, value: &Value<'gc>, ) -> Result> { let value = if let Ok(sockname) = String::from_value(ctx, *value) { let ret: Value<'gc> = ctx.get_global::>("muds")?.get(ctx, sockname)?; if ret.is_nil() { Err(Error::msg( "Could not find a MUD connection with that name.", ))?; } ret } else { *value }; Ok(UserData::from_value(ctx, value)? .downcast::]>()? .as_ref() .clone()) } pub fn handle_websocket_output( socket: &WebSocketId, engine: &mut LuaState, input: &[u8], ) -> Result<(), String> { engine .interp .try_enter(|ctx| { let handlers: Table = ctx.get_global("handlers")?; let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudoutput"))?; ctx.fetch(&engine.exec).restart( ctx, input_fn, ( intern_id::(ctx, socket.clone()), ctx.intern(input), ), ); Ok(()) }) .map_err(|err| format!("{}", err))?; engine .interp .execute::<()>(&engine.exec) .map_err(|err| format!("{}", err)) } pub(super) fn mudclose<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Value<'gc> = stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing argument to mudclose"))?; let conntab: Table<'gc> = Table::from_value( ctx, Table::from_value(ctx, ctx.get_global("muds")?)?.get_value(ctx, mud), )?; let seq = piccolo::async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); async move { call_checking_metatable::( &mut seq, conntab.clone(), "closed", &[], ) .await?; Ok(piccolo::SequenceReturn::Return) } }); Ok(piccolo::CallbackReturn::Sequence(seq)) }) } pub fn handle_websocket_output_log_err( socket: &WebSocketId, globals: &GlobalMemoCell, input: &[u8], ) { match handle_websocket_output(socket, &mut globals.lua_engine.borrow_mut(), input) { Ok(()) => {} Err(e) => console::log_2( &JsValue::from_str("An error occurred calling the WebSocket input handler"), &JsValue::from_str(&e), ), } execute_queue(globals); } pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Result<(), String> { engine .interp .try_enter(|ctx| { let handlers: Table = ctx.get_global("handlers")?; let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudclose"))?; ctx.fetch(&engine.exec).restart( ctx, input_fn, intern_id::(ctx, socket.clone()), ); Ok(()) }) .map_err(|err| format!("{}", err))?; engine .interp .execute::<()>(&engine.exec) .map_err(|err| format!("{}", err)) } pub fn handle_websocket_close_log_err(socket: &WebSocketId, globals: &GlobalMemoCell) { match handle_websocket_close(socket, &mut globals.lua_engine.borrow_mut()) { Ok(()) => {} Err(e) => console::log_2( &JsValue::from_str("An error occurred calling the WebSocket close handler"), &JsValue::from_str(&e), ), } execute_queue(globals); } pub(super) fn sendmud_raw<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: WebSocketId = try_unwrap_socketid( ctx, &stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing MUD argument"))?, )?; let msg: piccolo::String = stack.from_front(ctx)?; send_message_to_mud(&mud, msg.as_bytes(), &global_memo)?; Ok(piccolo::CallbackReturn::Return) }) } pub(super) fn connect_mud<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mut trusted: bool = false; let mut naws: bool = true; let name: String = loop { let v: String = stack.from_front(ctx)?; if v == "-trust" { trusted = true; continue; } if v == "-no_naws" { naws = false; continue; } break v; }; let name: Value<'gc> = ctx.intern(name.as_bytes()).into(); let muds: Table = ctx.get_global("muds")?; if !muds.get_value(ctx, name).is_nil() { Err(Error::msg( "Attempt to create MUD connection using name that's already taken", ))? } let url: String = stack.from_front(ctx)?; let new_socket = intern_id( ctx, connect_websocket( trusted, &url, &mut global_memo.ws_registry.borrow_mut(), &global_memo, )?, ); muds.set(ctx, name, new_socket)?; let mud_class: Table = ctx .get_global::("classes")? .get(ctx, ctx.intern_static(b"mud"))?; let conntab = Table::new(&ctx); muds.set(ctx, new_socket, conntab)?; conntab.set(ctx, ctx.intern_static(b"socket"), new_socket)?; conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern_static(b""))?; conntab.set_metatable(&ctx, Some(mud_class)); let curframe: Value = ctx .get_global::
("info")? .get(ctx, ctx.intern_static(b"current_frame"))?; if let Ok(curframe) = try_unwrap_frame(ctx, &curframe) { if let Ok(frame) = ctx .get_global::
("frames")? .get::(ctx, curframe.0 as i64) { if !frame.is_nil() { Table::from_value(ctx, frame)?.set( ctx, ctx.intern_static(b"linked_mud"), conntab, )?; } } } if naws { set_option_supported(ctx, &conntab, &NAWS_TELOPT, &Side::Us); } set_option_supported(ctx, &conntab, &TERMTYPE_TELOPT, &Side::Us); set_option_supported(ctx, &conntab, &EOR_TELOPT, &Side::Him); // Call conntab:new... let seq = async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); async move { call_checking_metatable::(&mut seq, conntab, "new", &[]).await?; Ok(SequenceReturn::Return) } }); Ok(piccolo::CallbackReturn::Sequence(seq)) }) } pub(super) fn close_mud<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud_value: Value<'gc> = stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing MUD value argument"))?; let socket_id = try_unwrap_socketid(ctx, &mud_value)?; match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) { None => Err(Error::msg("That MUD connection doesn't exist."))?, Some(v) => { if v.closed { Err(Error::msg("That MUD connection was already closed."))? } v.connection.close().unwrap_or(()); v.retained_closures = None; v.closed = true; } } Ok(piccolo::CallbackReturn::Return) }) } pub(super) fn delete_mud<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let name: String = stack.from_front(ctx)?; let name: Value<'gc> = ctx.intern(name.as_bytes()).into(); let muds: Table = ctx.get_global("muds")?; let mud_value = muds.get_value(ctx, name); if mud_value.is_nil() { Err(Error::msg( "Attempt to delete MUD connection that wasn't found", ))? } let socket_id = try_unwrap_socketid(ctx, &mud_value)?; // Delete the MUD data if possible. let _ = muds.set_raw(&ctx, mud_value, Value::Nil)?; for (k, v) in muds.iter() { match UserData::from_value(ctx, v) { Err(_) => continue, Ok(ud) => match ud.downcast:: Gc<'gcb, WebSocketId>]>() { Err(_) => continue, Ok(v) if v.as_ref() != &socket_id => continue, _ => {} }, } let _ = muds.set_raw(&ctx, k, Value::Nil); } unintern_id(ctx, &socket_id); match global_memo.ws_registry.borrow_mut().remove(&socket_id) { None => {} Some(sockdat) if sockdat.closed => {} Some(sockdat) => sockdat.connection.close().unwrap_or(()), } Ok(piccolo::CallbackReturn::Return) }) } pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Value<'gc> = stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing argument to mudoutput"))?; let output: &'gc [u8] = stack.from_front::>(ctx)?.as_bytes(); let conntab: Table<'gc> = Table::from_value( ctx, Table::from_value(ctx, ctx.get_global("muds")?)?.get_value(ctx, mud), )?; let buf: &'gc [u8] = piccolo::String::from_value(ctx, conntab.get(ctx, ctx.intern_static(b"buffer"))?)? .as_bytes(); let mut cur_buf: Vec = [buf, output].concat(); let mut fns: Vec<(&'static str, Vec)> = vec![]; loop { match parse_telnet_buf(&cur_buf) { (new_buf, None) => { conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern(&new_buf))?; let seq = piccolo::async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); let fns: Vec<(&'static str, Vec)> = fns .into_iter() .map(|fnv| { ( fnv.0, fnv.1.into_iter().map(|v| locals.stash(&ctx, v)).collect(), ) }) .collect(); async move { for (func_name, params) in fns { console::log_1(&JsValue::from_str(&format!( "Calling {}", func_name ))); call_checking_metatable::( &mut seq, conntab.clone(), func_name, ¶ms, ) .await?; } Ok(piccolo::SequenceReturn::Return) } }); return Ok(piccolo::CallbackReturn::Sequence(seq)); } (new_buf, Some(cmd)) => { cur_buf = new_buf; match cmd { TelnetOutput::Line(l) => { fns.push(("mudoutput_line", vec![ctx.intern(&l).into_value(ctx)])) } TelnetOutput::Prompt(p) => { fns.push(("mudoutput_prompt", vec![ctx.intern(&p).into_value(ctx)])) } TelnetOutput::Nop => {} TelnetOutput::Break => fns.push(("mudoutput_break", vec![])), TelnetOutput::Sync => fns.push(("mudoutput_sync", vec![])), TelnetOutput::Interrupt => fns.push(("mudoutput_interrupt", vec![])), TelnetOutput::AbortOutput => fns.push(("mudoutput_abort_output", vec![])), TelnetOutput::AreYouThere => fns.push(("mudoutput_areyouthere", vec![])), TelnetOutput::Will(v) => { fns.push(("mudoutput_will", vec![v.into_value(ctx)])) } TelnetOutput::Wont(v) => { fns.push(("mudoutput_wont", vec![v.into_value(ctx)])) } TelnetOutput::Do(v) => fns.push(("mudoutput_do", vec![v.into_value(ctx)])), TelnetOutput::Dont(v) => { fns.push(("mudoutput_dont", vec![v.into_value(ctx)])) } TelnetOutput::Subnegotiation(t) => { fns.push(("mudoutput_subnegotiation", vec![t.into_value(ctx)])) } } } } } }) } pub(super) fn mudoutput_line<'gc>( ctx: Context<'gc>, _global_memo: &GlobalMemoCell, ) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud = Table::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line self"))?, )?; let line = piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line line"))?, )?; let frameroutes: Table = mud.get(ctx, "frameroutes")?; let triggers: UserData = mud.get(ctx, "triggers")?; let default_frame: Value = frameroutes .iter() .map(|(_k, v)| Ok::(Table::from_value(ctx, v)?)) .next() .unwrap_or_else(|| Ok(ctx.get_global::
("frames")?.get(ctx, 1_i64)?))? .get(ctx, "frame")?; let seq = async_sequence(&ctx, |locals, mut seq| { let frameroutes: Vec = frameroutes .iter() .filter_map(|fr| { Table::from_value(ctx, fr.1) .ok() .map(|v| locals.stash(&ctx, v)) }) .collect(); let line = locals.stash(&ctx, line.into_value(ctx)); let triggers = locals.stash(&ctx, triggers); let default_frame = locals.stash(&ctx, default_frame); async move { call_checking_metatable::( &mut seq, triggers, "try_run_sub", &[line.clone(), default_frame], ) .await?; for frameroute in frameroutes { call_checking_metatable::( &mut seq, frameroute, "route", &[line.clone()], ) .await?; } Ok(SequenceReturn::Return) } }); Ok(CallbackReturn::Sequence(seq)) }) } pub(super) fn mudoutput_prompt<'gc>( ctx: Context<'gc>, _global_memo: &GlobalMemoCell, ) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud = Table::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line self"))?, )?; let prompt = piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line line"))?, )?; let frameroutes: Table = mud.get(ctx, "frameroutes")?; let seq = async_sequence(&ctx, |locals, mut seq| { let frameroutes: Vec = frameroutes .iter() .filter_map(|fr| { Table::from_value(ctx, fr.1) .ok() .map(|v| locals.stash(&ctx, v)) }) .collect(); let line = locals.stash(&ctx, prompt.into_value(ctx)); async move { for frameroute in frameroutes { call_checking_metatable::( &mut seq, frameroute, "route", &[line.clone()], ) .await?; } Ok(SequenceReturn::Return) } }); Ok(CallbackReturn::Sequence(seq)) }) } fn name_telopt(ctx: Context, optno: u8) -> anyhow::Result { for (k, v) in ctx .get_global::
("info")? .get::<_, Table>(ctx, "telopts")? { if u8::from_value(ctx, v)? == optno { return Ok(String::from_value(ctx, k)?); } } Ok(format!("{}", optno)) } pub(super) fn mudoutput_will<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, optno): (Table, u8) = stack.consume(ctx)?; let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; let mut mud_memo = MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }; if handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)) { let seq = async_sequence(&ctx, move |locals, mut seq| { let mud = locals.stash(&ctx, mud); async move { let name = seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?; call_checking_metatable::( &mut seq, mud, format!("remote_{}_enabled", name), &[], ) .await?; Ok(SequenceReturn::Return) } }); Ok(CallbackReturn::Sequence(seq)) } else { Ok(CallbackReturn::Return) } }) } pub(super) fn mudoutput_wont<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, optno): (Table, u8) = stack.consume(ctx)?; let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; let mut mud_memo = MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }; handle_incoming_wont(ctx, &mut mud_memo, &mud, &Telopt(optno)); Ok(CallbackReturn::Return) }) } pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, optno): (Table, u8) = stack.consume(ctx)?; let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; let mut mud_memo = MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }; if handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)) { let seq = async_sequence(&ctx, move |locals, mut seq| { let mud = locals.stash(&ctx, mud); async move { let name = seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?; call_checking_metatable::( &mut seq, mud, format!("local_{}_enabled", name), &[], ) .await?; Ok(SequenceReturn::Return) } }); Ok(CallbackReturn::Sequence(seq)) } else { Ok(CallbackReturn::Return) } }) } pub(super) fn mudoutput_dont<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, optno): (Table, u8) = stack.consume(ctx)?; let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; let mut mud_memo = MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }; handle_incoming_dont(ctx, &mut mud_memo, &mud, &Telopt(optno)); Ok(CallbackReturn::Return) }) } pub(super) fn mudoutput_subnegotiation<'gc>(ctx: Context<'gc>) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, mut msg): (Table, Vec) = stack.consume(ctx)?; if msg.is_empty() { console::log_1(&JsValue::from_str( "Received invalid subnegotiation with no type", )); return Ok(CallbackReturn::Return); } let msg_type = msg.remove(0); let msg_typename = name_telopt(ctx, msg_type)?; let seq = async_sequence(&ctx, |locals, mut seq| { let mud = locals.stash(&ctx, mud); let msg = locals.stash(&ctx, msg.into_value(ctx)); async move { call_checking_metatable( &mut seq, mud, format!("mudoutput_subnegotiation_{}", &msg_typename), &[msg], ) .await?; Ok(SequenceReturn::Return) } }); Ok(CallbackReturn::Sequence(seq)) }) } pub(super) fn mudoutput_subnegotiation_termtype<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, mut msg): (Table, Vec) = stack.consume(ctx)?; if msg.is_empty() { return Ok(CallbackReturn::Return); } let cmd = msg.remove(0); let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; const SEND_CMD: u8 = 1; match cmd { SEND_CMD => { send_subnegotiation_if_allowed( ctx, &mud, &TERMTYPE_TELOPT, &Side::Us, &mut MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }, format!("\x00{}", "XTERM").as_bytes(), ); Ok(CallbackReturn::Return) } _ => Ok(CallbackReturn::Return), } }) } pub(super) fn mudinput_line<'gc>( ctx: Context<'gc>, _global_memo: &GlobalMemoCell, ) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, line): (Table, piccolo::String) = stack.consume(ctx)?; stack.push_back(mud.get::(ctx, ctx.intern_static(b"socket"))?); let line = [line.as_bytes(), b"\r\n"].concat(); stack.push_back(ctx.intern(&line).into_value(ctx)); let func = ctx .get_global::
("commands")? .get::(ctx, ctx.intern_static(b"sendmud_raw"))?; Ok(CallbackReturn::Call { function: func, then: None, }) }) } pub(super) fn mudclass_closed<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Table = stack.consume(ctx)?; let frameroutes: Table = mud.get(ctx, "frameroutes")?; let default_frame: Value = frameroutes .iter() .map(|(_k, v)| Ok::(Table::from_value(ctx, v)?)) .next() .unwrap_or_else(|| Ok(ctx.get_global::
("frames")?.get(ctx, 1_i64)?))? .get(ctx, "frame")?; let default_frame: FrameId = try_unwrap_frame(ctx, &default_frame)?; echo_to_term_frame( &global_memo, &default_frame, &format!("Connection to {} lost.\r\n", name_mud(ctx, &mud)), ) .map_err(anyhow::Error::msg)?; Ok(CallbackReturn::Return) }) } pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Table = Table::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("classes.mud:new missing object!"))?, )?; let frameroutes: Table = Table::new(&ctx); let frameroute: Table = Table::new(&ctx); frameroutes.set(ctx, 0, frameroute)?; frameroute.set_metatable( &ctx, Some( ctx.get_global::
("classes")? .get(ctx, ctx.intern_static(b"frameroute"))?, ), ); mud.set(ctx, ctx.intern_static(b"frameroutes"), frameroutes)?; let curr_frame: Value = ctx .get_global::
("info")? .get(ctx, ctx.intern_static(b"current_frame"))?; // And call new on the frameroute, for the current frame. let seq = async_sequence(&ctx, |locals, mut seq| { let frameroute = locals.stash(&ctx, frameroute); let curr_frame = locals.stash(&ctx, curr_frame); let create_match_tab = locals.stash(&ctx, Function::Callback(create_match_table(ctx))); let mud = locals.stash(&ctx, mud); async move { call_checking_metatable::( &mut seq, frameroute, "new", &[curr_frame], ) .await?; seq.call(&create_match_tab, 0).await?; seq.try_enter(|ctx, locals, _ex, mut stack| { let matchtab: Value = stack.consume(ctx)?; locals.fetch(&mud).set(ctx, "triggers", matchtab)?; Ok(()) })?; Ok(SequenceReturn::Return) } }); Ok(piccolo::CallbackReturn::Sequence(seq)) }) } pub(super) fn mud_trigger<'gc>(ctx: Context<'gc>) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let info: Table = ctx.get_global("info")?; let cur_frame_id: FrameId = try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?; let frames: Table = ctx.get_global("frames")?; let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?; let linked_mud: Value = cur_frame.get(ctx, "linked_mud")?; if linked_mud.is_nil() { Err(anyhow::Error::msg( "No MUD connection is linked to the current terminal. Triggers are per MUD and so can only be set after connecting.", ))?; } let linked_mud: Table = Table::from_value(ctx, linked_mud)?; let triggers: UserData = linked_mud.get(ctx, "triggers")?; if stack.is_empty() { return list_match_tab(cur_frame_id, triggers, ctx); } let trigger_match: piccolo::String = piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing alias match"))?, )?; let sub_to: piccolo::String = piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing substitution match"))?, )?; if !stack.is_empty() { Err(anyhow::Error::msg( "Extra arguments to trigger command. Try wrapping the action in {}", ))?; } stack.push_back(triggers.into_value(ctx)); stack.push_back(trigger_match.into_value(ctx)); stack.push_back(sub_to.into_value(ctx)); let seq = async_sequence(&ctx, |locals, mut seq| { let add_func = locals.stash(&ctx, Function::Callback(match_table_add(ctx))); async move { seq.call(&add_func, 0).await?; Ok(SequenceReturn::Return) } }); Ok(piccolo::CallbackReturn::Sequence(seq)) }) } pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let info: Table = ctx.get_global("info")?; let cur_frame_id: FrameId = try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?; let frames: Table = ctx.get_global("frames")?; let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?; let linked_mud: Value = cur_frame.get(ctx, "linked_mud")?; if linked_mud.is_nil() { Err(anyhow::Error::msg( "No MUD connection is linked to the current terminal. Triggers are per MUD and so can only be set after connecting.", ))?; } let linked_mud: Table = Table::from_value(ctx, linked_mud)?; let triggers: UserData = linked_mud.get(ctx, "triggers")?; let trigger_match: piccolo::String = piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("Missing trigger match"))?, )?; if !stack.is_empty() { Err(anyhow::Error::msg( "Extra arguments to unact command. Giving the commands to trigger is not necessary.", ))?; } stack.push_back(triggers.into_value(ctx)); stack.push_back(trigger_match.into_value(ctx)); let seq = async_sequence(&ctx, |locals, mut seq| { let remove_func = locals.stash(&ctx, Function::Callback(match_table_remove(ctx))); async move { seq.call(&remove_func, 0).await?; Ok(SequenceReturn::Return) } }); Ok(piccolo::CallbackReturn::Sequence(seq)) }) } pub(super) fn mud_term_resized<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let (mud, width, height): (Table, u16, u16) = stack.consume(ctx)?; let socket: Value = mud.get(ctx, "socket")?; let socket: &WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; send_subnegotiation_if_allowed( ctx, &mud, &NAWS_TELOPT, &Side::Us, &mut MudWithMemo { memo: global_memo.clone(), mud: socket.clone(), }, &[ (width >> 8) as u8, (width & 0xFF) as u8, (height >> 8) as u8, (height & 0xFF) as u8, ], ); Ok(CallbackReturn::Return) }) } pub(super) fn local_naws_enabled<'gc>( ctx: Context<'gc>, global_state_memo: &GlobalMemoCell, ) -> Callback<'gc> { let global_state_memo = global_state_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Table = stack.consume(ctx)?; let frameroutes: Table = mud.get(ctx, "frameroutes")?; let default_frame: FrameId = try_unwrap_frame( ctx, &frameroutes .iter() .map(|(_k, v)| Ok::(Table::from_value(ctx, v)?)) .next() .unwrap_or_else(|| Ok(ctx.get_global::
("frames")?.get(ctx, 1_i64)?))? .get(ctx, "frame")?, )?; let default_frame: Table = ctx .get_global::
("frames")? .get(ctx, default_frame.0 as i64)?; let width: Value = default_frame.get(ctx, "width")?; let height: Value = default_frame.get(ctx, "height")?; if width.is_nil() || height.is_nil() { return Ok(CallbackReturn::Return); } stack.push_back(mud.into_value(ctx)); stack.push_back(width); stack.push_back(height); let cb = mud_term_resized(ctx, &global_state_memo); Ok(CallbackReturn::Call { function: Function::Callback(cb), then: None, }) }) } pub(super) fn opt_enabled_noop<'gc>(ctx: Context<'gc>) -> Callback<'gc> { Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return)) } fn name_mud<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> String { fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result { let socket: Value<'gc> = mud.get(ctx, "socket")?; let socket: &'gc WebSocketId = UserData::from_value(ctx, socket)?.downcast::]>()?; for (k, v) in ctx.get_global::
("muds")?.iter() { if let Ok(v) = UserData::from_value(ctx, v) .map_err(anyhow::Error::from) .and_then(|ud| { ud.downcast::]>() .map_err(anyhow::Error::from) }) { if v.0 == socket.0 { return Ok(piccolo::String::from_value(ctx, k)?.to_str()?.to_owned()); } } } Err(anyhow::Error::msg("No match")) } match name_mud_internal(ctx, mud) { Ok(name) => name, Err(_) => "unknown MUD".to_owned(), } } pub(super) fn mud_log<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let name: String = stack.from_front(ctx)?; let muds: Table = ctx.get_global("muds")?; let mud_value: Value = muds.get(ctx, name)?; if mud_value.is_nil() { Err(Error::msg( "Attempt to delete MUD connection that wasn't found", ))? } let log_dest: String = stack.from_back(ctx)?; let log_dest = if log_dest == "none" || log_dest == "off" { None } else { Some(log_dest) }; let socket_id = try_unwrap_socketid(ctx, &mud_value)?; match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) { None => Err(Error::msg("That MUD connection doesn't exist."))?, Some(v) => { v.log_dest = log_dest; } } Ok(CallbackReturn::Return) }) } pub(super) fn cmd_list_logs<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, _stack| { let frame = try_unwrap_frame( ctx, &ctx.get_global::
("info")? .get::<&str, Value>(ctx, "current_frame")?, )?; start_listing_logs(&global_memo, &frame); Ok(CallbackReturn::Return) }) } pub(super) fn cmd_delete_logs<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let frame = try_unwrap_frame( ctx, &ctx.get_global::
("info")? .get::<&str, Value>(ctx, "current_frame")?, )?; let stream = String::from_utf8( piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?, )? .as_bytes() .to_vec(), )?; let (min_date, max_date) = match stack.len() { 2 => { let min_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; let max_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; (min_date, max_date) } 1 => { let max_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; ("0000-00-00".to_owned(), max_date) } 0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()), _ => Err(anyhow::Error::msg( "At most three arguments expected: stream min-date max-date", ))?, }; start_deleting_logs(&global_memo, &frame, &stream, &min_date, &max_date); Ok(CallbackReturn::Return) }) } pub(super) fn cmd_download_logs<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, _global_layout: &UseStateSetter, ) -> Callback<'gc> { let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let frame = try_unwrap_frame( ctx, &ctx.get_global::
("info")? .get::<&str, Value>(ctx, "current_frame")?, )?; let stream = String::from_utf8( piccolo::String::from_value( ctx, stack .pop_front() .ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?, )? .as_bytes() .to_vec(), )?; let (min_date, max_date) = match stack.len() { 2 => { let min_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; let max_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; (min_date, max_date) } 1 => { let max_date = String::from_utf8( piccolo::String::from_value(ctx, stack.pop_front().unwrap())? .as_bytes() .to_vec(), )?; ("0000-00-00".to_owned(), max_date) } 0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()), _ => Err(anyhow::Error::msg( "At most three arguments expected: stream min-date max-date", ))?, }; start_downloading_logs(&global_memo, &frame, &stream, &min_date, &max_date); Ok(CallbackReturn::Return) }) } pub fn connect_default_mud(global_memo: &GlobalMemoCell) -> anyhow::Result<()> { if let Some(window) = window() { if let Ok(host) = window.location().host() { let mut engine = global_memo.lua_engine.borrow_mut(); let engine: &mut LuaState = &mut engine; engine.try_set_current_frame(&FrameId(1))?; engine.interp.try_enter(|ctx| { let connect_function: Function = ctx .get_global::
("commands")? .get(ctx, "connect_mud")?; ctx.fetch(&engine.exec).restart( ctx, connect_function, ("-trust", "default", format!("wss://{}/ws", &host)), ); Ok(()) })?; engine.interp.execute::<()>(&engine.exec)?; } } Ok(()) }