feat: run http server to receive commands
This commit is contained in:
parent
a937db0be6
commit
5752e94ad2
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1180,6 +1180,9 @@ dependencies = [
|
||||
"azalea",
|
||||
"clap",
|
||||
"futures",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
@ -1493,6 +1496,12 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
@ -1505,6 +1514,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
|
@ -20,6 +20,9 @@ anyhow = "1"
|
||||
azalea = { git = "https://github.com/azalea-rs/azalea.git" }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
futures = "0"
|
||||
http-body-util = "0"
|
||||
hyper = { version = "1", features = ["server"] }
|
||||
hyper-util = "0"
|
||||
log = { version = "0" }
|
||||
mlua = { version = "0", features = ["async", "luau", "send"] }
|
||||
parking_lot = { version = "0" }
|
||||
|
@ -1,5 +1,5 @@
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
/// A Minecraft utility bot
|
||||
#[derive(Parser)]
|
||||
@ -7,4 +7,8 @@ pub struct Arguments {
|
||||
/// Path to main Lua file
|
||||
#[arg(short, long)]
|
||||
pub script: Option<PathBuf>,
|
||||
|
||||
/// Socket address to bind HTTP server to
|
||||
#[arg(short, long)]
|
||||
pub address: Option<SocketAddr>,
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::{State, commands::CommandSource, scripting};
|
||||
use crate::{State, commands::CommandSource, http::handle, scripting};
|
||||
use azalea::prelude::*;
|
||||
use log::info;
|
||||
use hyper::{server::conn::http1, service::service_fn};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use log::{error, info};
|
||||
use mlua::Function;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
|
||||
let globals = state.lua.lock().globals();
|
||||
@ -40,7 +43,25 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow:
|
||||
inner: Some(client),
|
||||
},
|
||||
)?;
|
||||
globals.get::<Function>("Init")?.call::<()>(())?
|
||||
globals.get::<Function>("Init")?.call::<()>(())?;
|
||||
|
||||
if let Some(address) = state.address {
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
let state = state.clone();
|
||||
let service = service_fn(move |request| {
|
||||
let state = state.clone();
|
||||
async move { handle(request, state).await }
|
||||
});
|
||||
|
||||
if let Err(error) = http1::Builder::new().serve_connection(io, service).await {
|
||||
error!("failed to serve connection: {error:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
90
src/http.rs
Normal file
90
src/http.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::State;
|
||||
use http_body_util::{BodyExt, Empty, Full, combinators::BoxBody};
|
||||
use hyper::{Method, Request, Response, StatusCode, body::Bytes};
|
||||
|
||||
pub async fn handle(
|
||||
request: Request<hyper::body::Incoming>,
|
||||
state: State,
|
||||
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
|
||||
let path = request.uri().path().to_owned();
|
||||
|
||||
match (request.method(), path.as_str()) {
|
||||
(&Method::POST, "/reload") => {
|
||||
let lua = state.lua.lock();
|
||||
let config_path = match lua.globals().get::<String>("config_path") {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
return Ok(status_code_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Some(full(format!(
|
||||
"failed to get config_path from lua globals: {error:?}"
|
||||
))),
|
||||
));
|
||||
}
|
||||
};
|
||||
if let Err(error) = match &std::fs::read_to_string(&config_path) {
|
||||
Ok(string) => lua.load(string).exec(),
|
||||
Err(error) => {
|
||||
return Ok(status_code_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Some(full(format!("failed to read {config_path:?}: {error:?}"))),
|
||||
));
|
||||
}
|
||||
} {
|
||||
return Ok(status_code_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Some(full(format!(
|
||||
"failed to execute configuration as lua code: {error:?}"
|
||||
))),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Response::new(empty()))
|
||||
}
|
||||
|
||||
(&Method::POST, "/eval" | "/exec") => {
|
||||
let bytes = request.into_body().collect().await?.to_bytes();
|
||||
let code = match std::str::from_utf8(&bytes) {
|
||||
Ok(code) => code,
|
||||
Err(error) => {
|
||||
return Ok(status_code_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(full(format!("invalid utf-8 data received: {error:?}"))),
|
||||
));
|
||||
}
|
||||
};
|
||||
let chunk = state.lua.lock().load(code);
|
||||
|
||||
Ok(Response::new(full(match path.as_str() {
|
||||
"/eval" => format!("{:?}", chunk.eval::<String>()),
|
||||
"/exec" => format!("{:?}", chunk.exec()),
|
||||
_ => unreachable!(),
|
||||
})))
|
||||
}
|
||||
|
||||
(&Method::GET, "/ping") => Ok(Response::new(full("pong!"))),
|
||||
|
||||
_ => Ok(status_code_response(StatusCode::NOT_FOUND, None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn status_code_response(
|
||||
status_code: StatusCode,
|
||||
bytes: Option<BoxBody<Bytes, hyper::Error>>,
|
||||
) -> Response<BoxBody<Bytes, hyper::Error>> {
|
||||
let mut response = Response::new(bytes.unwrap_or(empty()));
|
||||
*response.status_mut() = status_code;
|
||||
response
|
||||
}
|
||||
|
||||
fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> {
|
||||
Full::new(chunk.into())
|
||||
.map_err(|never| match never {})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn empty() -> BoxBody<Bytes, hyper::Error> {
|
||||
Empty::<Bytes>::new()
|
||||
.map_err(|never| match never {})
|
||||
.boxed()
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
mod arguments;
|
||||
mod commands;
|
||||
mod events;
|
||||
mod http;
|
||||
mod scripting;
|
||||
|
||||
use azalea::{brigadier::prelude::CommandDispatcher, prelude::*};
|
||||
@ -11,12 +12,13 @@ use commands::{CommandSource, register};
|
||||
use events::handle_event;
|
||||
use mlua::Lua;
|
||||
use parking_lot::Mutex;
|
||||
use std::{path::PathBuf, process::ExitCode, sync::Arc};
|
||||
use std::{net::SocketAddr, path::PathBuf, process::ExitCode, sync::Arc};
|
||||
|
||||
#[derive(Default, Clone, Component)]
|
||||
pub struct State {
|
||||
lua: Arc<Mutex<Lua>>,
|
||||
commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
|
||||
lua: Arc<Mutex<Lua>>,
|
||||
address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@ -73,8 +75,9 @@ async fn main() -> ExitCode {
|
||||
let Err(error) = ClientBuilder::new()
|
||||
.set_handler(handle_event)
|
||||
.set_state(State {
|
||||
lua: Arc::new(Mutex::new(lua)),
|
||||
commands: Arc::new(commands),
|
||||
lua: Arc::new(Mutex::new(lua)),
|
||||
address: args.address,
|
||||
})
|
||||
.start(account, server.as_ref())
|
||||
.await;
|
||||
|
Loading…
x
Reference in New Issue
Block a user