diff --git a/Cargo.lock b/Cargo.lock index 4e1bc82..a8ce227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -60,6 +66,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "approx" version = "0.5.1" @@ -317,6 +372,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "combine" version = "4.6.7" @@ -537,6 +598,29 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -727,6 +811,9 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" name = "group-bot" version = "0.1.0" dependencies = [ + "env_logger", + "hashbrown 0.14.5", + "log", "serde", "tokio", "toml", @@ -760,6 +847,11 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", + "equivalent", +] [[package]] name = "heck" @@ -884,6 +976,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "1.3.1" @@ -1027,6 +1125,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -2407,6 +2511,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 0210899..61d700c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ veloren-client = { git = "https://gitlab.com/veloren/veloren", branch = "master" veloren-common-net = { git = "https://gitlab.com/veloren/veloren", branch = "master" } toml = "0.8.14" serde = { version = "1.0.203", features = ["derive"] } +env_logger = "0.11.3" +log = "0.4.21" +hashbrown = { version = "0.14.5", features = ["equivalent"] } [patch.crates-io] specs = { git = "https://github.com/amethyst/specs.git", rev = "4e2da1df29ee840baa9b936593c45592b7c9ae27" } diff --git a/src/bot.rs b/src/bot.rs index 5c143f6..e10942f 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,7 +1,8 @@ -use std::{sync::Arc, time::Duration}; +use std::{collections::VecDeque, sync::Arc, time::Duration}; +use log::{debug, info}; use tokio::runtime::Runtime; -use veloren_client::{addr::ConnectionArgs, Client, Event}; +use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent}; use veloren_common::{ clock::Clock, comp::{invite::InviteKind, ChatType, ControllerInputs}, @@ -13,11 +14,21 @@ use veloren_common_net::msg::PlayerInfo; use crate::Config; +enum Event { + Admin(String), + Ban(String), + Cheese, + Invite(Uid), + Kick(Uid), + Unban(String), +} + pub struct Bot { client: Client, clock: Clock, admin_list: Vec, ban_list: Vec, + events: VecDeque, } impl Bot { @@ -27,6 +38,8 @@ impl Bot { admin_list: Vec, ban_list: Vec, ) -> Result { + info!("Connecting to veloren"); + let client = connect_to_veloren(username, password)?; let clock = Clock::new(Duration::from_secs_f64(0.1)); @@ -35,10 +48,13 @@ impl Bot { clock, admin_list, ban_list, + events: VecDeque::new(), }) } pub fn select_character(&mut self) -> Result<(), String> { + info!("Selecting a character"); + self.client.load_character_list(); while self.client.character_list().loading { @@ -70,13 +86,23 @@ impl Bot { } pub fn tick(&mut self) -> Result<(), String> { - let events = self + let veloren_events = self .client .tick(ControllerInputs::default(), self.clock.dt()) .map_err(|error| format!("{error:?}"))?; - for event in events { - self.handle_event(event)?; + debug!("Tick! Handling {} bot events", self.events.len()); + + while !self.events.is_empty() { + if let Some(event) = self.events.pop_front() { + self.handle_event(event)?; + } + } + + debug!("Tick! Handling {} veloren events", veloren_events.len()); + + for veloren_event in veloren_events { + self.handle_veloren_event(veloren_event)?; } self.client.cleanup(); @@ -85,8 +111,8 @@ impl Bot { Ok(()) } - fn handle_event(&mut self, event: Event) -> Result<(), String> { - if let Event::Chat(message) = event { + fn handle_veloren_event(&mut self, event: VelorenEvent) -> Result<(), String> { + if let VelorenEvent::Chat(message) = event { match message.chat_type { ChatType::Tell(sender, _) | ChatType::Group(sender, _) => { let sender_info = self.client.player_list().get(&sender).unwrap().clone(); @@ -105,59 +131,106 @@ impl Bot { fn handle_message(&mut self, content: &str, sender: &PlayerInfo) -> Result<(), String> { let mut words = content.split_whitespace(); + let command = if let Some(command) = words.next() { + command + } else { + return Ok(()); + }; - if let Some(command) = words.next() { - match command { - "admin" => { - if self.admin_list.contains(&sender.uuid) || self.admin_list.is_empty() { - self.adminify_players(words)?; + match command { + "admin" => { + if self.admin_list.contains(&sender.uuid) || self.admin_list.is_empty() { + for word in words { + self.events.push_back(Event::Admin(word.to_string())); } } - "ban" => { - if self.admin_list.contains(&sender.uuid) { - self.kick_players(words.clone()); - self.ban_players(words)?; + } + "ban" => { + if self.admin_list.contains(&sender.uuid) { + for word in words { + let uid = self.find_uid(word)?; + + self.events.push_back(Event::Kick(uid.clone())); + self.events.push_back(Event::Ban(word.to_string())); } } - "cheese" => self - .client - .send_command("group".to_string(), vec!["I love cheese!".to_string()]), - "inv" => { - if !self.ban_list.contains(&sender.uuid) { - if content == "inv" { - self.invite_players([sender.player_alias.as_str()].into_iter()); - } else { - self.invite_players(words); + } + "cheese" => { + let uid = self.find_uid(&sender.player_alias)?; + + if self.client.group_members().contains_key(&uid) { + self.events.push_back(Event::Cheese); + } + } + "inv" => { + if !self.ban_list.contains(&sender.uuid) { + if content == "inv" { + let uid = self.find_uid(&sender.player_alias)?; + + self.events.push_back(Event::Invite(uid)); + } else { + for word in words { + let uid = self.find_uid(word)?; + + self.events.push_back(Event::Invite(uid)); } } } - "kick" => { - if self.admin_list.contains(&sender.uuid) { - self.kick_players(words); + } + "kick" => { + if self.admin_list.contains(&sender.uuid) { + for word in words { + let uid = self.find_uid(word)?; + + self.events.push_back(Event::Kick(uid)); } } - "unban" => { - if self.admin_list.contains(&sender.uuid) { - self.unban_players(words)?; + } + "unban" => { + if self.admin_list.contains(&sender.uuid) { + for word in words { + self.events.push_back(Event::Unban(word.to_string())); } } - _ => {} + } + _ => {} + } + + Ok(()) + } + + fn handle_event(&mut self, event: Event) -> Result<(), String> { + match event { + Event::Admin(name) => { + self.adminify_player(&name)?; + } + Event::Ban(name) => { + self.ban_player(&name)?; + } + Event::Cheese => self + .client + .send_command("group".to_string(), vec!["I love cheese!".to_string()]), + Event::Invite(uid) => { + self.client.send_invite(uid, InviteKind::Group); + } + Event::Kick(uid) => { + self.client.kick_from_group(uid); + } + Event::Unban(name) => { + self.unban_player(&name)?; } } Ok(()) } - fn adminify_players<'a, T: Iterator>( - &mut self, - names: T, - ) -> Result<(), String> { - for name in names { - if let Some(uuid) = self.find_uuid(&name) { - if !self.admin_list.contains(&uuid) && !self.ban_list.contains(&uuid) { - self.admin_list.push(uuid); - } - } + fn adminify_player(&mut self, name: &str) -> Result<(), String> { + info!("Adminifying {name}"); + + let uuid = self.find_uuid(name)?; + + if !self.admin_list.contains(&uuid) && !self.ban_list.contains(&uuid) { + self.admin_list.push(uuid); } let old_config = Config::read()?; @@ -173,13 +246,13 @@ impl Bot { Ok(()) } - fn ban_players<'a, T: Iterator>(&mut self, names: T) -> Result<(), String> { - for name in names { - if let Some(uuid) = self.find_uuid(&name) { - if !self.admin_list.contains(&uuid) && !self.ban_list.contains(&uuid) { - self.ban_list.push(uuid); - } - } + fn ban_player(&mut self, name: &str) -> Result<(), String> { + info!("Banning {name}"); + + let uuid = self.find_uuid(name)?; + + if !self.admin_list.contains(&uuid) && !self.ban_list.contains(&uuid) { + self.ban_list.push(uuid); } let old_config = Config::read()?; @@ -195,43 +268,18 @@ impl Bot { Ok(()) } - fn invite_players<'a, T: Iterator>(&mut self, names: T) { - for name in names { - if let Some(player_id) = self.find_uid(name) { - self.client - .send_invite(player_id.clone(), InviteKind::Group); - } else { - eprintln!("{name} could not be invited") - } - } - } + fn unban_player(&mut self, name: &str) -> Result<(), String> { + info!("Unbanning {name}"); - fn kick_players<'a, T: Iterator>(&mut self, names: T) { - for name in names { - if let Some(uid) = self.find_uid(&name) { - if let Some(uuid) = self.find_uuid(&name) { - if !self.admin_list.contains(&uuid) { - self.client.kick_from_group(uid.clone()); - } - } - } - } - } + let uuid = self.find_uuid(name)?; - fn unban_players<'a, T: Iterator>(&mut self, names: T) -> Result<(), String> { - let uuids = names - .filter_map(|name| self.find_uuid(name)) - .collect::>(); - - for uuid in uuids { - if let Some(index) = self - .ban_list - .iter() - .enumerate() - .find_map(|(index, banned)| if &uuid == banned { Some(index) } else { None }) - { - self.ban_list.remove(index); - } + if let Some(uuid) = self + .ban_list + .iter() + .enumerate() + .find_map(|(index, banned)| if &uuid == banned { Some(index) } else { None }) + { + self.ban_list.remove(uuid); } let old_config = Config::read()?; @@ -247,24 +295,32 @@ impl Bot { Ok(()) } - fn find_uid<'a>(&'a self, name: &str) -> Option<&'a Uid> { - self.client.player_list().iter().find_map(|(id, info)| { - if info.player_alias == name { - Some(id) - } else { - None - } - }) + fn find_uid(&self, name: &str) -> Result { + self.client + .player_list() + .iter() + .find_map(|(id, info)| { + if info.player_alias == name { + Some(id.clone()) + } else { + None + } + }) + .ok_or(format!("Failed to find uid for player {}", name)) } - fn find_uuid(&self, name: &str) -> Option { - self.client.player_list().iter().find_map(|(_, info)| { - if info.player_alias == name { - Some(info.uuid) - } else { - None - } - }) + fn find_uuid(&self, name: &str) -> Result { + self.client + .player_list() + .iter() + .find_map(|(_, info)| { + if info.player_alias == name { + Some(info.uuid) + } else { + None + } + }) + .ok_or(format!("Failed to find uid for player {}", name)) } } diff --git a/src/main.rs b/src/main.rs index f4002b5..08e8d65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,8 @@ impl Config { } fn main() { + env_logger::init(); + let config = Config::read().unwrap(); let mut bot = Bot::new( &config.username,