//! Azalea is a framework for creating Minecraft bots. //! //! Internally, it's just a wrapper over [`azalea_client`], adding useful //! functions for making bots. Because of this, lots of the documentation will //! refer to `azalea_client`. You can just replace these with `azalea` in your //! code, since everything from azalea_client is re-exported in azalea. //! //! # Installation //! //! First, install Rust nightly with `rustup install nightly` and `rustup //! default nightly`. //! //! Then, add one of the following lines to your Cargo.toml.\ //! Latest bleeding-edge version: //! `azalea = { git="https://github.com/mat-1/Cargo.toml" }` //! Latest "stable" release: //! `azalea = "0.3"` //! //! # Examples //! //! ```rust,no_run //! //! A bot that logs chat messages sent in the server to the console. //! //! use azalea::prelude::*; //! use parking_lot::Mutex; //! use std::sync::Arc; //! //! #[tokio::main] //! async fn main() { //! let account = Account::offline("bot"); //! // or Account::microsoft("example@example.com").await.unwrap(); //! //! azalea::start(azalea::Options { //! account, //! address: "localhost", //! state: State::default(), //! plugins: vec![], //! handle, //! }) //! .await //! .unwrap(); //! } //! //! #[derive(Default, Clone)] //! pub struct State {} //! //! async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { //! match event { //! Event::Chat(m) => { //! println!(m.message().to_ansi(None)); //! } //! _ => {} //! } //! //! Ok(()) //! } //! ``` //! //! [`azalea_client`]: https://crates.io/crates/azalea-client mod bot; pub mod prelude; use async_trait::async_trait; pub use azalea_client::*; use azalea_protocol::ServerAddress; use std::future::Future; use thiserror::Error; /// Plugins can keep their own personal state, listen to events, and add new functions to Client. #[async_trait] pub trait Plugin: Send + Sync + PluginClone + 'static { async fn handle(self: Box, event: Event, bot: Client); } /// An internal trait that allows Plugin to be cloned. #[doc(hidden)] pub trait PluginClone { fn clone_box(&self) -> Box; } impl PluginClone for T where T: 'static + Plugin + Clone, { fn clone_box(&self) -> Box { Box::new(self.clone()) } } impl Clone for Box { fn clone(&self) -> Self { self.clone_box() } } pub type HandleFn = fn(Client, Event, S) -> Fut; /// The options that are passed to [`azalea::start`]. /// /// [`azalea::start`]: fn.start.html pub struct Options where A: TryInto, Fut: Future>, { /// The address of the server that we're connecting to. This can be a /// `&str`, [`ServerAddress`], or anything that implements /// `TryInto`. /// /// [`ServerAddress`]: azalea_protocol::ServerAddress pub address: A, /// The account that's going to join the server. pub account: Account, /// A list of plugins that are going to be used. Plugins are external /// crates that add extra functionality to Azalea. pub plugins: Vec>, /// A struct that contains the data that you want your bot to remember /// across events. /// /// # Examples /// /// ```rust /// use parking_lot::Mutex; /// use std::sync::Arc; /// /// #[derive(Default, Clone)] /// struct State { /// farming: Arc>, /// } /// ``` pub state: S, /// The function that's called whenever we get an event. /// /// # Examples /// /// ```rust /// use azalea::prelude::*; /// /// async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { /// Ok(()) /// } /// ``` pub handle: HandleFn, } #[derive(Error, Debug)] pub enum Error { #[error("Invalid address")] InvalidAddress, #[error("Join error: {0}")] Join(#[from] azalea_client::JoinError), } /// Join a server and start handling events. This function will run forever until /// it gets disconnected from the server. /// /// # Examples /// /// ```rust,no_run /// let error = azalea::start(azalea::Options { /// account, /// address: "localhost", /// state: State::default(), /// plugins: vec![Box::new(autoeat::Plugin::default())], /// handle, /// }).await; /// ``` pub async fn start< S: Send + Sync + Clone + 'static, A: Send + TryInto, Fut: Future> + Send + 'static, >( options: Options, ) -> Result<(), Error> { let address = match options.address.try_into() { Ok(address) => address, Err(_) => return Err(Error::InvalidAddress), }; let (bot, mut rx) = Client::join(&options.account, address).await?; let state = options.state; let bot_plugin = bot::Plugin::default(); while let Some(event) = rx.recv().await { for plugin in &options.plugins { let plugin = plugin.clone(); tokio::spawn(plugin.handle(event.clone(), bot.clone())); } tokio::spawn(bot::Plugin::handle( Box::new(bot_plugin.clone()), event.clone(), bot.clone(), )); tokio::spawn((options.handle)(bot.clone(), event.clone(), state.clone())); } Ok(()) }