Add TOML config support
This commit is contained in:
parent
158feb17c5
commit
6b86c032ee
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
target/
|
target/
|
||||||
password.txt
|
config.toml
|
||||||
|
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -727,7 +727,9 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
|||||||
name = "group-bot"
|
name = "group-bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"veloren-client",
|
"veloren-client",
|
||||||
"veloren-common",
|
"veloren-common",
|
||||||
]
|
]
|
||||||
@ -1938,6 +1940,15 @@ dependencies = [
|
|||||||
"syn 2.0.66",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.8"
|
||||||
@ -2227,6 +2238,40 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.22.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@ -2879,6 +2924,15 @@ version = "0.52.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.50.0"
|
version = "0.50.0"
|
||||||
|
@ -7,6 +7,8 @@ edition = "2021"
|
|||||||
tokio = "1.38.0"
|
tokio = "1.38.0"
|
||||||
veloren-common = { git = "https://gitlab.com/veloren/veloren", branch = "master", features = ["no-assets"] }
|
veloren-common = { git = "https://gitlab.com/veloren/veloren", branch = "master", features = ["no-assets"] }
|
||||||
veloren-client = { git = "https://gitlab.com/veloren/veloren", branch = "master" }
|
veloren-client = { git = "https://gitlab.com/veloren/veloren", branch = "master" }
|
||||||
|
toml = "0.8.14"
|
||||||
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
specs = { git = "https://github.com/amethyst/specs.git", rev = "4e2da1df29ee840baa9b936593c45592b7c9ae27" }
|
specs = { git = "https://github.com/amethyst/specs.git", rev = "4e2da1df29ee840baa9b936593c45592b7c9ae27" }
|
||||||
|
207
src/bot.rs
Normal file
207
src/bot.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use veloren_client::{addr::ConnectionArgs, Client, Event};
|
||||||
|
use veloren_common::{
|
||||||
|
clock::Clock,
|
||||||
|
comp::{invite::InviteKind, ChatType, ControllerInputs},
|
||||||
|
uid::Uid,
|
||||||
|
ViewDistances,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
pub struct Bot {
|
||||||
|
client: Client,
|
||||||
|
clock: Clock,
|
||||||
|
admin_list: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bot {
|
||||||
|
pub fn new(username: &str, password: &str, admin_list: Vec<String>) -> Result<Self, String> {
|
||||||
|
let client = connect_to_veloren(username, password)?;
|
||||||
|
let clock = Clock::new(Duration::from_secs_f64(1.0));
|
||||||
|
|
||||||
|
Ok(Bot {
|
||||||
|
client,
|
||||||
|
clock,
|
||||||
|
admin_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_character(&mut self) -> Result<(), String> {
|
||||||
|
self.client.load_character_list();
|
||||||
|
|
||||||
|
while self.client.character_list().loading {
|
||||||
|
self.client
|
||||||
|
.tick(ControllerInputs::default(), self.clock.dt())
|
||||||
|
.map_err(|error| format!("{error:?}"))?;
|
||||||
|
self.clock.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
let character_id = self
|
||||||
|
.client
|
||||||
|
.character_list()
|
||||||
|
.characters
|
||||||
|
.first()
|
||||||
|
.expect("No characters to select")
|
||||||
|
.character
|
||||||
|
.id
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.client.request_character(
|
||||||
|
character_id,
|
||||||
|
ViewDistances {
|
||||||
|
terrain: 0,
|
||||||
|
entity: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) -> Result<(), String> {
|
||||||
|
let events = self
|
||||||
|
.client
|
||||||
|
.tick(ControllerInputs::default(), self.clock.dt())
|
||||||
|
.expect("Failed to run client.");
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
self.handle_event(event)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.cleanup();
|
||||||
|
self.clock.tick();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, event: Event) -> Result<(), String> {
|
||||||
|
if let Event::Chat(message) = event {
|
||||||
|
match message.chat_type {
|
||||||
|
ChatType::Tell(sender, _) | ChatType::Group(sender, _) => {
|
||||||
|
let sender_uuid = self
|
||||||
|
.client
|
||||||
|
.player_list()
|
||||||
|
.get(&sender)
|
||||||
|
.unwrap()
|
||||||
|
.uuid
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
self.handle_message(
|
||||||
|
message.into_content().as_plain().unwrap_or(""),
|
||||||
|
&sender_uuid,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message(&mut self, content: &str, sender: &String) -> Result<(), String> {
|
||||||
|
let mut words = content.split_whitespace();
|
||||||
|
|
||||||
|
if let Some(command) = words.next() {
|
||||||
|
match command {
|
||||||
|
"admin" => {
|
||||||
|
if self.admin_list.contains(sender) {
|
||||||
|
self.adminify_players(words)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"inv" => self.invite_players(words),
|
||||||
|
"kick" => {
|
||||||
|
if self.admin_list.contains(sender) {
|
||||||
|
self.kick_players(words)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adminify_players<'a, T: Iterator<Item = &'a str>>(
|
||||||
|
&mut self,
|
||||||
|
names: T,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
for name in names {
|
||||||
|
if let Some(player_id) = self.find_uuid(&name) {
|
||||||
|
self.admin_list.push(player_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_config = Config::read();
|
||||||
|
let new_config = Config {
|
||||||
|
username: old_config.username,
|
||||||
|
password: old_config.password,
|
||||||
|
admin_list: self.admin_list.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
new_config.write()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invite_players<'a, T: Iterator<Item = &'a str>>(&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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kick_players<'a, T: Iterator<Item = &'a str>>(&mut self, names: T) {
|
||||||
|
for name in names {
|
||||||
|
if let Some(player_id) = self.find_uid(&name) {
|
||||||
|
self.client.kick_from_group(player_id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_uuid(&self, name: &str) -> Option<String> {
|
||||||
|
self.client.player_list().iter().find_map(|(_, info)| {
|
||||||
|
if info.player_alias == name {
|
||||||
|
Some(info.uuid.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_to_veloren(username: &str, password: &str) -> Result<Client, String> {
|
||||||
|
let runtime = Arc::new(Runtime::new().unwrap());
|
||||||
|
let runtime2 = Arc::clone(&runtime);
|
||||||
|
|
||||||
|
runtime
|
||||||
|
.block_on(Client::new(
|
||||||
|
ConnectionArgs::Tcp {
|
||||||
|
hostname: "server.veloren.net".to_string(),
|
||||||
|
prefer_ipv6: false,
|
||||||
|
},
|
||||||
|
runtime2,
|
||||||
|
&mut None,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
None,
|
||||||
|
|provider| provider == "https://auth.veloren.net",
|
||||||
|
&|_| {},
|
||||||
|
|_| {},
|
||||||
|
Default::default(),
|
||||||
|
))
|
||||||
|
.map_err(|error| format!("{error:?}"))
|
||||||
|
}
|
209
src/main.rs
209
src/main.rs
@ -1,191 +1,44 @@
|
|||||||
|
mod bot;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env::var,
|
env::var,
|
||||||
fs::{read_to_string, write},
|
fs::{read_to_string, write},
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::runtime::Runtime;
|
use bot::Bot;
|
||||||
use veloren_client::{addr::ConnectionArgs, Client, Event};
|
use serde::{Deserialize, Serialize};
|
||||||
use veloren_common::{
|
|
||||||
clock::Clock,
|
|
||||||
comp::{invite::InviteKind, ChatType, ControllerInputs},
|
|
||||||
uuid::Uuid,
|
|
||||||
ViewDistances,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CRABO_UUID: &str = "3127d1eb-72ea-4342-a4fa-06dd2fb0bad9";
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub admin_list: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
fn read() -> Self {
|
||||||
|
let config_path = var("CONFIG_PATH").expect("Provide CONFIG_PATH environment variable");
|
||||||
|
let config_file_content = read_to_string(config_path).expect("Failed to read config file");
|
||||||
|
|
||||||
|
toml::from_str::<Config>(&config_file_content).expect("Failed to deserialize config file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self) -> Result<(), String> {
|
||||||
|
let config_path = var("CONFIG_PATH").expect("Provide CONFIG_PATH environment variable");
|
||||||
|
let config_string = toml::to_string(self).expect("Failed to serialize Config");
|
||||||
|
|
||||||
|
write(config_path, config_string).map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let password_file = var("PASSWORD_FILE").expect("Provide PASSWORD_FILE environment variable.");
|
let config = Config::read();
|
||||||
let password = read_to_string(&password_file)
|
let mut bot = Bot::new(&config.username, &config.password, config.admin_list)
|
||||||
.expect(&format!("Failed to read password from {password_file}"));
|
.expect("Failed to create bot");
|
||||||
|
|
||||||
let mut admin_list = vec![Uuid::parse_str(CRABO_UUID).unwrap()];
|
bot.select_character().expect("Failed to select character");
|
||||||
|
|
||||||
write("admin_list.txt", format!("{admin_list:?}"))
|
|
||||||
.expect("Failed to write initial admin list.");
|
|
||||||
|
|
||||||
let mut client = connect_to_veloren(password);
|
|
||||||
let mut clock = Clock::new(Duration::from_secs_f64(1.0));
|
|
||||||
|
|
||||||
select_character(&mut client, &mut clock);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let events = client
|
bot.tick().expect("Failed to run bot")
|
||||||
.tick(ControllerInputs::default(), clock.dt())
|
|
||||||
.expect("Failed to run client.");
|
|
||||||
|
|
||||||
for event in events {
|
|
||||||
if let Event::Chat(message) = event {
|
|
||||||
match message.chat_type {
|
|
||||||
ChatType::Tell(sender, _) | ChatType::Group(sender, _) => {
|
|
||||||
let sender_uuid = client.player_list().get(&sender).unwrap().uuid;
|
|
||||||
|
|
||||||
handle_message(
|
|
||||||
&mut client,
|
|
||||||
&mut admin_list,
|
|
||||||
message.into_content().as_plain().unwrap_or(""),
|
|
||||||
sender_uuid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.cleanup();
|
|
||||||
clock.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect_to_veloren(password: String) -> Client {
|
|
||||||
let runtime = Arc::new(Runtime::new().unwrap());
|
|
||||||
let runtime2 = Arc::clone(&runtime);
|
|
||||||
|
|
||||||
runtime
|
|
||||||
.block_on(Client::new(
|
|
||||||
ConnectionArgs::Tcp {
|
|
||||||
hostname: "server.veloren.net".to_string(),
|
|
||||||
prefer_ipv6: false,
|
|
||||||
},
|
|
||||||
runtime2,
|
|
||||||
&mut None,
|
|
||||||
"crabobot",
|
|
||||||
&password,
|
|
||||||
None,
|
|
||||||
|provider| provider == "https://auth.veloren.net",
|
|
||||||
&|_| {},
|
|
||||||
|_| {},
|
|
||||||
Default::default(),
|
|
||||||
))
|
|
||||||
.expect("Failed to create client instance.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_character(client: &mut Client, clock: &mut Clock) {
|
|
||||||
client.load_character_list();
|
|
||||||
|
|
||||||
while client.character_list().loading {
|
|
||||||
println!("Loading characters...");
|
|
||||||
|
|
||||||
client
|
|
||||||
.tick(ControllerInputs::default(), clock.dt())
|
|
||||||
.expect("Failed to run client.");
|
|
||||||
clock.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
let character_id = client
|
|
||||||
.character_list()
|
|
||||||
.characters
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.character
|
|
||||||
.id
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
client.request_character(
|
|
||||||
character_id,
|
|
||||||
ViewDistances {
|
|
||||||
terrain: 0,
|
|
||||||
entity: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_message(
|
|
||||||
mut client: &mut Client,
|
|
||||||
mut admin_list: &mut Vec<Uuid>,
|
|
||||||
content: &str,
|
|
||||||
sender: Uuid,
|
|
||||||
) {
|
|
||||||
let mut words = content.split_whitespace();
|
|
||||||
|
|
||||||
if let Some(command) = words.next() {
|
|
||||||
match command {
|
|
||||||
"admin" => {
|
|
||||||
if admin_list.contains(&sender) {
|
|
||||||
adminify_players(&mut client, &mut admin_list, words)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"inv" => invite_players(&mut client, words),
|
|
||||||
"kick" => {
|
|
||||||
if admin_list.contains(&sender) {
|
|
||||||
kick_players(&mut client, words)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn adminify_players<'a, T: Iterator<Item = &'a str>>(
|
|
||||||
client: &mut Client,
|
|
||||||
admin_list: &mut Vec<Uuid>,
|
|
||||||
names: T,
|
|
||||||
) {
|
|
||||||
for name in names {
|
|
||||||
let find_id = client.player_list().iter().find_map(|(_, info)| {
|
|
||||||
if info.player_alias == name {
|
|
||||||
Some(info.uuid)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(player_id) = find_id {
|
|
||||||
admin_list.push(player_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invite_players<'a, T: Iterator<Item = &'a str>>(client: &mut Client, names: T) {
|
|
||||||
for name in names {
|
|
||||||
let find_id = client.player_list().iter().find_map(|(id, info)| {
|
|
||||||
if info.player_alias == name {
|
|
||||||
Some(id)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(player_id) = find_id {
|
|
||||||
client.send_invite(player_id.clone(), InviteKind::Group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kick_players<'a, T: Iterator<Item = &'a str>>(client: &mut Client, names: T) {
|
|
||||||
for name in names {
|
|
||||||
let find_id = client.player_list().iter().find_map(|(id, info)| {
|
|
||||||
if info.player_alias == name {
|
|
||||||
Some(id)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(player_id) = find_id {
|
|
||||||
client.kick_from_group(player_id.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user