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",
|
"azalea",
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
"log",
|
"log",
|
||||||
"mlua",
|
"mlua",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@ -1493,6 +1496,12 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1505,6 +1514,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -20,6 +20,9 @@ anyhow = "1"
|
|||||||
azalea = { git = "https://github.com/azalea-rs/azalea.git" }
|
azalea = { git = "https://github.com/azalea-rs/azalea.git" }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
futures = "0"
|
futures = "0"
|
||||||
|
http-body-util = "0"
|
||||||
|
hyper = { version = "1", features = ["server"] }
|
||||||
|
hyper-util = "0"
|
||||||
log = { version = "0" }
|
log = { version = "0" }
|
||||||
mlua = { version = "0", features = ["async", "luau", "send"] }
|
mlua = { version = "0", features = ["async", "luau", "send"] }
|
||||||
parking_lot = { version = "0" }
|
parking_lot = { version = "0" }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::path::PathBuf;
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
/// A Minecraft utility bot
|
/// A Minecraft utility bot
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -7,4 +7,8 @@ pub struct Arguments {
|
|||||||
/// Path to main Lua file
|
/// Path to main Lua file
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub script: Option<PathBuf>,
|
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 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 mlua::Function;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
|
pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
|
||||||
let globals = state.lua.lock().globals();
|
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),
|
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 arguments;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod events;
|
mod events;
|
||||||
|
mod http;
|
||||||
mod scripting;
|
mod scripting;
|
||||||
|
|
||||||
use azalea::{brigadier::prelude::CommandDispatcher, prelude::*};
|
use azalea::{brigadier::prelude::CommandDispatcher, prelude::*};
|
||||||
@ -11,12 +12,13 @@ use commands::{CommandSource, register};
|
|||||||
use events::handle_event;
|
use events::handle_event;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
use parking_lot::Mutex;
|
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)]
|
#[derive(Default, Clone, Component)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
lua: Arc<Mutex<Lua>>,
|
|
||||||
commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
|
commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
|
||||||
|
lua: Arc<Mutex<Lua>>,
|
||||||
|
address: Option<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -73,8 +75,9 @@ async fn main() -> ExitCode {
|
|||||||
let Err(error) = ClientBuilder::new()
|
let Err(error) = ClientBuilder::new()
|
||||||
.set_handler(handle_event)
|
.set_handler(handle_event)
|
||||||
.set_state(State {
|
.set_state(State {
|
||||||
lua: Arc::new(Mutex::new(lua)),
|
|
||||||
commands: Arc::new(commands),
|
commands: Arc::new(commands),
|
||||||
|
lua: Arc::new(Mutex::new(lua)),
|
||||||
|
address: args.address,
|
||||||
})
|
})
|
||||||
.start(account, server.as_ref())
|
.start(account, server.as_ref())
|
||||||
.await;
|
.await;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user