Compare commits
No commits in common. "2040eb00787d1a1a2c59aeec0ec5b7143d7334a0" and "e7300e1a37dfab2f59587fb536d01f7d4088aa11" have entirely different histories.
2040eb0078
...
e7300e1a37
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1769,7 +1769,6 @@ dependencies = [
|
|||||||
"mlua",
|
"mlua",
|
||||||
"ncr",
|
"ncr",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"zip",
|
"zip",
|
||||||
|
@ -40,7 +40,6 @@ mimalloc = { version = "0", optional = true }
|
|||||||
mlua = { version = "0", features = ["async", "luajit", "send"] }
|
mlua = { version = "0", features = ["async", "luajit", "send"] }
|
||||||
ncr = { version = "0", features = ["cfb8", "ecb", "gcm"] }
|
ncr = { version = "0", features = ["cfb8", "ecb", "gcm"] }
|
||||||
parking_lot = "0"
|
parking_lot = "0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["macros"] }
|
tokio = { version = "1", features = ["macros"] }
|
||||||
zip = { version = "2", default-features = false, features = ["flate2"] }
|
zip = { version = "2", default-features = false, features = ["flate2"] }
|
||||||
|
@ -5,6 +5,7 @@ A Minecraft bot with Lua scripting support, written in Rust with [azalea](https:
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Running Lua from
|
- Running Lua from
|
||||||
|
- `errornowatcher.lua`
|
||||||
- in-game chat messages
|
- in-game chat messages
|
||||||
- Matrix chat messages
|
- Matrix chat messages
|
||||||
- POST requests to HTTP server
|
- POST requests to HTTP server
|
||||||
@ -24,4 +25,4 @@ $ cargo build --release
|
|||||||
$ # ./target/release/errornowatcher
|
$ # ./target/release/errornowatcher
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure the `Server` and `Username` globals are defined in `main.lua` before starting the bot.
|
Make sure the `Server` and `Username` globals are defined in `errornowatcher.lua` before starting the bot.
|
||||||
|
@ -6,11 +6,11 @@ use std::path::PathBuf;
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version = build_info::version_formatted())]
|
#[command(version = build_info::version_formatted())]
|
||||||
pub struct Arguments {
|
pub struct Arguments {
|
||||||
/// Path to main Lua file
|
/// Path to Lua entry point
|
||||||
#[arg(short, long)]
|
#[arg(short, long, default_value = "errornowatcher.lua")]
|
||||||
pub script: Option<PathBuf>,
|
pub script: PathBuf,
|
||||||
|
|
||||||
/// Code to execute (after script)
|
/// Code to execute after loading script
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub exec: Option<String>,
|
pub exec: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -210,12 +210,12 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> Result<
|
|||||||
exit(0);
|
exit(0);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
#[cfg(feature = "matrix")]
|
|
||||||
matrix_init(&client, state.clone());
|
|
||||||
|
|
||||||
let globals = state.lua.globals();
|
let globals = state.lua.globals();
|
||||||
lua_init(client, &state, &globals).await?;
|
lua_init(client, &state, &globals).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "matrix")]
|
||||||
|
matrix_init(state.clone(), &globals);
|
||||||
|
|
||||||
let Some(address): Option<SocketAddr> = globals
|
let Some(address): Option<SocketAddr> = globals
|
||||||
.get::<String>("HttpAddress")
|
.get::<String>("HttpAddress")
|
||||||
.ok()
|
.ok()
|
||||||
@ -276,17 +276,13 @@ async fn lua_init(client: Client, state: &State, globals: &Table) -> Result<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "matrix")]
|
#[cfg(feature = "matrix")]
|
||||||
fn matrix_init(client: &Client, state: State) {
|
fn matrix_init(state: State, globals: &Table) {
|
||||||
let globals = state.lua.globals();
|
|
||||||
if let Ok(homeserver_url) = globals.get::<String>("MatrixHomeserverUrl")
|
if let Ok(homeserver_url) = globals.get::<String>("MatrixHomeserverUrl")
|
||||||
&& let Ok(username) = globals.get::<String>("MatrixUsername")
|
&& let Ok(username) = globals.get::<String>("MatrixUsername")
|
||||||
&& let Ok(password) = globals.get::<String>("MatrixPassword")
|
&& let Ok(password) = globals.get::<String>("MatrixPassword")
|
||||||
{
|
{
|
||||||
let name = client.username();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(error) =
|
if let Err(error) = matrix::login(state, homeserver_url, username, &password).await {
|
||||||
matrix::login(homeserver_url, username, &password, state, globals, name).await
|
|
||||||
{
|
|
||||||
error!("failed to log into matrix account: {error:?}");
|
error!("failed to log into matrix account: {error:?}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
23
src/main.rs
23
src/main.rs
@ -27,7 +27,6 @@ use clap::Parser;
|
|||||||
use commands::{CommandSource, register};
|
use commands::{CommandSource, register};
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
use futures_locks::RwLock;
|
use futures_locks::RwLock;
|
||||||
use log::debug;
|
|
||||||
use mlua::{Function, Lua, Table};
|
use mlua::{Function, Lua, Table};
|
||||||
use replay::{plugin::RecordPlugin, recorder::Recorder};
|
use replay::{plugin::RecordPlugin, recorder::Recorder};
|
||||||
use std::{
|
use std::{
|
||||||
@ -59,28 +58,24 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let event_listeners = Arc::new(RwLock::new(HashMap::new()));
|
let event_listeners = Arc::new(RwLock::new(HashMap::new()));
|
||||||
let lua = unsafe { Lua::unsafe_new() };
|
let lua = unsafe { Lua::unsafe_new() };
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
lua::register_globals(&lua, &globals, event_listeners.clone())?;
|
|
||||||
|
|
||||||
if let Some(path) = args.script {
|
lua::register_globals(&lua, &globals, event_listeners.clone())?;
|
||||||
globals.set("SCRIPT_PATH", &*path)?;
|
globals.set("SCRIPT_PATH", &*args.script)?;
|
||||||
lua.load(read_to_string(path)?).exec()?;
|
lua.load(
|
||||||
} else if let Some(code) = ["main.lua", "errornowatcher.lua"].iter().find_map(|path| {
|
read_to_string(&args.script)
|
||||||
debug!("trying to load code from {path}");
|
.with_context(|| format!("failed to read {}", args.script.display()))?,
|
||||||
globals.set("SCRIPT_PATH", *path).ok()?;
|
)
|
||||||
read_to_string(path).ok()
|
.exec()?;
|
||||||
}) {
|
|
||||||
lua.load(code).exec()?;
|
|
||||||
}
|
|
||||||
if let Some(code) = args.exec {
|
if let Some(code) = args.exec {
|
||||||
lua.load(code).exec()?;
|
lua.load(code).exec()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = globals
|
let server = globals
|
||||||
.get::<String>("Server")
|
.get::<String>("Server")
|
||||||
.context("lua globals missing Server variable")?;
|
.expect("Server should be in lua globals");
|
||||||
let username = globals
|
let username = globals
|
||||||
.get::<String>("Username")
|
.get::<String>("Username")
|
||||||
.context("lua globals missing Username variable")?;
|
.expect("Username should be in lua globals");
|
||||||
|
|
||||||
let mut commands = CommandDispatcher::new();
|
let mut commands = CommandDispatcher::new();
|
||||||
register(&mut commands);
|
register(&mut commands);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::MatrixContext;
|
use super::{COMMAND_PREFIX, Context};
|
||||||
use crate::{
|
use crate::{
|
||||||
events::call_listeners,
|
events::call_listeners,
|
||||||
lua::{self, matrix::room::Room as LuaRoom},
|
lua::{self, matrix::room::Room as LuaRoom},
|
||||||
@ -19,7 +19,7 @@ use tokio::time::sleep;
|
|||||||
pub async fn on_regular_room_message(
|
pub async fn on_regular_room_message(
|
||||||
event: OriginalSyncRoomMessageEvent,
|
event: OriginalSyncRoomMessageEvent,
|
||||||
room: Room,
|
room: Room,
|
||||||
ctx: Ctx<MatrixContext>,
|
ctx: Ctx<Context>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if room.state() != RoomState::Joined {
|
if room.state() != RoomState::Joined {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -35,9 +35,9 @@ pub async fn on_regular_room_message(
|
|||||||
.get::<Vec<String>>("MatrixOwners")
|
.get::<Vec<String>>("MatrixOwners")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.contains(&event.sender.to_string())
|
.contains(&event.sender.to_string())
|
||||||
&& text_content.body.starts_with(&ctx.name)
|
&& text_content.body.starts_with(COMMAND_PREFIX)
|
||||||
{
|
{
|
||||||
let body = text_content.body[ctx.name.len()..]
|
let body = text_content.body[COMMAND_PREFIX.len()..]
|
||||||
.trim_start_matches(':')
|
.trim_start_matches(':')
|
||||||
.trim();
|
.trim();
|
||||||
let split = body.split_once(char::is_whitespace).unzip();
|
let split = body.split_once(char::is_whitespace).unzip();
|
||||||
@ -90,7 +90,7 @@ pub async fn on_stripped_state_member(
|
|||||||
member: StrippedRoomMemberEvent,
|
member: StrippedRoomMemberEvent,
|
||||||
client: Client,
|
client: Client,
|
||||||
room: Room,
|
room: Room,
|
||||||
ctx: Ctx<MatrixContext>,
|
ctx: Ctx<Context>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(user_id) = client.user_id()
|
if let Some(user_id) = client.user_id()
|
||||||
&& member.state_key == user_id
|
&& member.state_key == user_id
|
||||||
|
@ -2,122 +2,56 @@ mod bot;
|
|||||||
mod verification;
|
mod verification;
|
||||||
|
|
||||||
use crate::{State, lua::matrix::client::Client as LuaClient};
|
use crate::{State, lua::matrix::client::Client as LuaClient};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use bot::{on_regular_room_message, on_stripped_state_member};
|
use bot::{on_regular_room_message, on_stripped_state_member};
|
||||||
use log::{error, warn};
|
use matrix_sdk::{Client, config::SyncSettings};
|
||||||
use matrix_sdk::{
|
use std::{fs, sync::Arc};
|
||||||
Client, Error, LoopCtrl, authentication::matrix::MatrixSession, config::SyncSettings,
|
|
||||||
};
|
|
||||||
use mlua::Table;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{path::Path, sync::Arc};
|
|
||||||
use tokio::fs;
|
|
||||||
use verification::{on_device_key_verification_request, on_room_message_verification_request};
|
use verification::{on_device_key_verification_request, on_room_message_verification_request};
|
||||||
|
|
||||||
|
const COMMAND_PREFIX: &str = "ErrorNoWatcher";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MatrixContext {
|
pub struct Context {
|
||||||
state: State,
|
state: State,
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
struct Session {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
sync_token: Option<String>,
|
|
||||||
user_session: MatrixSession,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn persist_sync_token(
|
|
||||||
session_file: &Path,
|
|
||||||
session: &mut Session,
|
|
||||||
sync_token: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
session.sync_token = Some(sync_token);
|
|
||||||
fs::write(session_file, serde_json::to_string(&session)?).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
|
state: State,
|
||||||
homeserver_url: String,
|
homeserver_url: String,
|
||||||
username: String,
|
username: String,
|
||||||
password: &str,
|
password: &str,
|
||||||
state: State,
|
|
||||||
globals: Table,
|
|
||||||
name: String,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let root_dir = dirs::data_dir()
|
let mut client = Client::builder().homeserver_url(homeserver_url);
|
||||||
.context("no data directory")?
|
if let Some(db_path) = dirs::data_dir().map(|path| path.join("errornowatcher").join("matrix"))
|
||||||
.join("errornowatcher")
|
&& fs::create_dir_all(&db_path).is_ok()
|
||||||
.join(&name)
|
|
||||||
.join("matrix");
|
|
||||||
|
|
||||||
let mut builder = Client::builder().homeserver_url(homeserver_url);
|
|
||||||
if !fs::try_exists(&root_dir).await.unwrap_or_default()
|
|
||||||
&& let Err(error) = fs::create_dir_all(&root_dir).await
|
|
||||||
{
|
{
|
||||||
warn!("failed to create directory for matrix sqlite3 store: {error:?}");
|
client = client.sqlite_store(db_path, None);
|
||||||
} else {
|
|
||||||
builder = builder.sqlite_store(&root_dir, None);
|
|
||||||
}
|
}
|
||||||
let client = builder.build().await?;
|
|
||||||
|
|
||||||
let mut new_session;
|
let client = Arc::new(client.build().await?);
|
||||||
let session_file = root_dir.join("session.json");
|
client
|
||||||
let mut sync_settings = SyncSettings::default();
|
.matrix_auth()
|
||||||
if let Some(session) = fs::read_to_string(&session_file)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(|data| serde_json::from_str::<Session>(&data).ok())
|
|
||||||
{
|
|
||||||
new_session = session.clone();
|
|
||||||
if let Some(sync_token) = session.sync_token {
|
|
||||||
sync_settings = sync_settings.token(sync_token);
|
|
||||||
}
|
|
||||||
client.restore_session(session.user_session).await?;
|
|
||||||
} else {
|
|
||||||
let matrix_auth = client.matrix_auth();
|
|
||||||
matrix_auth
|
|
||||||
.login_username(username, password)
|
.login_username(username, password)
|
||||||
.initial_device_display_name(&name)
|
.device_id("ERRORNOWATCHER")
|
||||||
|
.initial_device_display_name("ErrorNoWatcher")
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
new_session = Session {
|
|
||||||
user_session: matrix_auth.session().context("should have session")?,
|
|
||||||
sync_token: None,
|
|
||||||
};
|
|
||||||
fs::write(&session_file, serde_json::to_string(&new_session)?).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.add_event_handler_context(MatrixContext { state, name });
|
|
||||||
client.add_event_handler(on_stripped_state_member);
|
client.add_event_handler(on_stripped_state_member);
|
||||||
loop {
|
let response = client.sync_once(SyncSettings::default()).await?;
|
||||||
match client.sync_once(sync_settings.clone()).await {
|
|
||||||
Ok(response) => {
|
|
||||||
sync_settings = sync_settings.token(response.next_batch.clone());
|
|
||||||
persist_sync_token(&session_file, &mut new_session, response.next_batch).await?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
error!("failed to do initial sync: {error:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.add_event_handler(on_device_key_verification_request);
|
client.add_event_handler(on_device_key_verification_request);
|
||||||
client.add_event_handler(on_room_message_verification_request);
|
client.add_event_handler(on_room_message_verification_request);
|
||||||
client.add_event_handler(on_regular_room_message);
|
client.add_event_handler(on_regular_room_message);
|
||||||
|
|
||||||
let client = Arc::new(client);
|
state
|
||||||
globals.set("matrix", LuaClient(client.clone()))?;
|
.lua
|
||||||
|
.globals()
|
||||||
|
.set("matrix", LuaClient(client.clone()))?;
|
||||||
|
|
||||||
|
client.add_event_handler_context(Context { state });
|
||||||
client
|
client
|
||||||
.sync_with_result_callback(sync_settings, |sync_result| async {
|
.sync(SyncSettings::default().token(response.next_batch))
|
||||||
let mut new_session = new_session.clone();
|
|
||||||
persist_sync_token(&session_file, &mut new_session, sync_result?.next_batch)
|
|
||||||
.await
|
|
||||||
.map_err(|err| Error::UnknownError(err.into()))?;
|
|
||||||
Ok(LoopCtrl::Continue)
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user