make the bot
This commit is contained in:
parent
54123fa669
commit
86d3ccd864
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
|
@ -11,4 +11,17 @@ tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
simple_logger = "4.2"
|
simple_logger = "4.2"
|
||||||
toml = "0.7"
|
toml = "0.7"
|
||||||
|
azalea-protocol = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
bevy_ecs = "0.11"
|
||||||
|
bevy = "0.11"
|
||||||
|
bevy_app = "0.11"
|
||||||
|
uuid = "1.3"
|
||||||
|
azalea-crypto = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
azalea-core = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
azalea-buf = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
azalea-client = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
parking_lot = "0.12"
|
||||||
|
reqwest = "0.11"
|
||||||
|
futures = "0.3"
|
||||||
|
regex = "1.8.1"
|
|
@ -0,0 +1,313 @@
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Arc};
|
||||||
|
use azalea_client::chat::ChatPacket;
|
||||||
|
use azalea_client::Client;
|
||||||
|
use log::info;
|
||||||
|
use crate::config::{Config, load_config, save_config};
|
||||||
|
use serenity::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub const BACON_FEATURE_FLAGS: &[&str] = &["F-A", "F-Q", "F-E", "F-T", "F-B", "C-B", "C-A", "C-O", "C-H", "C-S", "C-A-S", "C-A-C", "C-A-I", "C-A-U"];
|
||||||
|
|
||||||
|
pub async fn handle_message(p: ChatPacket, client: &mut Client, config: &Arc<RwLock<Config>>) {
|
||||||
|
if p.username().is_none() { return; }
|
||||||
|
|
||||||
|
{
|
||||||
|
let c = config.read().await;
|
||||||
|
|
||||||
|
info!("{} {} {:?}", p.username().unwrap(), p.uuid().unwrap(), c.relay.ignored);
|
||||||
|
|
||||||
|
if c.relay.ignored.contains(&p.username().unwrap()) || c.relay.ignored.contains(&p.uuid().unwrap().to_string()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = p.content().to_string();
|
||||||
|
|
||||||
|
let mut components = vec![];
|
||||||
|
|
||||||
|
let mut is_parsing_string = false;
|
||||||
|
let mut is_parsing_escape = false;
|
||||||
|
let mut component = "".to_string();
|
||||||
|
for char in message.chars() {
|
||||||
|
let mut char = char.to_string();
|
||||||
|
if !&is_parsing_escape && !&is_parsing_string {
|
||||||
|
if char == "\"" {
|
||||||
|
is_parsing_string = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if char == "\\" {
|
||||||
|
is_parsing_escape = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if char == " " {
|
||||||
|
components.push(component);
|
||||||
|
component = "".to_string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
component += char.as_str();
|
||||||
|
}
|
||||||
|
if is_parsing_escape {
|
||||||
|
if char == "\"" {
|
||||||
|
is_parsing_escape = false;
|
||||||
|
component += "\"";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if char == "n" {
|
||||||
|
is_parsing_escape = false;
|
||||||
|
component += "\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if char == "\\" {
|
||||||
|
is_parsing_escape = false;
|
||||||
|
component += "\\";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
client.chat(&format!("[bot] Sorry, I don't understand that message. (invalid escape char)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if is_parsing_string {
|
||||||
|
if char == "\"" {
|
||||||
|
is_parsing_string = false;
|
||||||
|
components.push(component);
|
||||||
|
component = "".to_string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
component += char.as_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !component.is_empty() {
|
||||||
|
components.push(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_parsing_string {
|
||||||
|
client.chat("[bot] Sorry, I don't understand that message. (unclosed string)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_for_me = components[0].starts_with(&format!("!@{}", config.read().await.relay.username)) || (!components[0].starts_with("!@") && components[0].starts_with("!"));
|
||||||
|
|
||||||
|
if !is_for_me { return; }
|
||||||
|
|
||||||
|
if components[0] == format!("!@{}", config.read().await.relay.username) && components.len() == 1 {
|
||||||
|
client.chat("[bot] Sorry, I don't understand that message. (missing command)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = if components[0] == format!("!@{}", config.read().await.relay.username) {
|
||||||
|
components[1].clone()
|
||||||
|
} else {
|
||||||
|
components[0].split('!').nth(1).unwrap().to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
match command.as_str() {
|
||||||
|
"bacon" => {
|
||||||
|
client.chat("[bacon] F-A F-Q F-E F-T F-B C-B C-A C-O C-H C-S C-A-I C-A-U");
|
||||||
|
client.chat("[bacon end]");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
"bot" => {
|
||||||
|
client.chat("[iambot]");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
"about" => {
|
||||||
|
client.chat("[bot about] Hi, I'm CuberCore! I am a bot that provides the chat relay for this server as well as many other useful features.");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
"owner" => {
|
||||||
|
client.chat(&format!("[bot] I am owned by: {} ({})", config.read().await.relay.owner_username, config.read().await.relay.owner_uuid_undashed));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
"help" => {
|
||||||
|
client.chat("[bot help] Here are my commands:");
|
||||||
|
client.chat("[bot help] !bacon, !bot, !about, !owner, !help, !sleep, !ignore [player], !unignore [player], !disconnect, !reconnect, !reload, !op [uuid], !deop [uuid], !location, !location [location], !location [location] [newlocation], !location [location] remove");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
"reload" => {
|
||||||
|
client.chat("[bot] Reloading configuration...");
|
||||||
|
|
||||||
|
{
|
||||||
|
let _ = std::mem::replace(config.write().await.deref_mut(), load_config());
|
||||||
|
}
|
||||||
|
|
||||||
|
client.chat("[bot] Configuration reloaded!");
|
||||||
|
},
|
||||||
|
"ignore" => {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !ignore [player]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = components[1].clone();
|
||||||
|
|
||||||
|
{
|
||||||
|
let has = { config.write().await.relay.ignored.contains(&player) };
|
||||||
|
if !has {
|
||||||
|
config.write().await.relay.ignored.push(player.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("[bot] {} has been ignored", player));
|
||||||
|
},
|
||||||
|
"unignore" => {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !unignore [player]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = components[1].clone();
|
||||||
|
|
||||||
|
{
|
||||||
|
let has = { config.write().await.relay.ignored.contains(&player) };
|
||||||
|
if has {
|
||||||
|
let index = { config.write().await.relay.ignored.iter().position(|u| u == &player).unwrap() };
|
||||||
|
config.write().await.relay.ignored.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("[bot] {} has been unignored", player));
|
||||||
|
},
|
||||||
|
"op" => {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !op [uuid]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = components[1].clone();
|
||||||
|
let player = match Uuid::from_str(&player) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
client.chat(&format!("[bot] invalid uuid: {}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let has = { config.write().await.permissions.admin.uuid_members.contains(&player) };
|
||||||
|
if !has {
|
||||||
|
config.write().await.permissions.admin.uuid_members.push(player.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("[bot] {} has been opped", player));
|
||||||
|
},
|
||||||
|
"deop" => {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !deop [player]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = components[1].clone();
|
||||||
|
let player = match Uuid::from_str(&player) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
client.chat(&format!("[bot] invalid uuid: {}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let has = { config.write().await.permissions.admin.uuid_members.contains(&player) };
|
||||||
|
if has {
|
||||||
|
let index = { config.write().await.permissions.admin.uuid_members.iter().position(|u| u == &player).unwrap() };
|
||||||
|
config.write().await.permissions.admin.uuid_members.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("[bot] {} has been deopped", player));
|
||||||
|
},
|
||||||
|
"location" => {
|
||||||
|
if components.len() == 1 {
|
||||||
|
client.chat(&format!("Here are the locations I know about: {:?}", config.read().await.locations.keys().collect::<Vec<_>>()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() == 2 {
|
||||||
|
if let Some(pos) = config.read().await.locations.get(&components[1]) {
|
||||||
|
client.chat(&format!("{} is located at {}!", components[1], pos));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
client.chat(&format!("Sorry, I don't know where {} is.", components[1]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() == 3 {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// !location abode "1 2 3"
|
||||||
|
// !location abode remove
|
||||||
|
|
||||||
|
if components[2] == "remove" {
|
||||||
|
{
|
||||||
|
config.write().await.locations.remove(&components[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("Okay, I removed the {} location.", components[1]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
config.write().await.locations.insert(components[1].clone(), components[2].clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("Okay, I set the {} location to {}.", components[1], components[2]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::{Arc};
|
||||||
|
use serenity::prelude::*;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use azalea_buf::{McBufReadable, McBufVarReadable, McBufVarWritable};
|
||||||
|
use azalea_client::{Account, Client, DefaultPlugins, Event, start_ecs};
|
||||||
|
use azalea_client::packet_handling::DeathEvent;
|
||||||
|
use azalea_client::respawn::{perform_respawn, PerformRespawnEvent};
|
||||||
|
use azalea_core::ResourceLocation;
|
||||||
|
use azalea_protocol::connect::Connection;
|
||||||
|
use azalea_protocol::packets::ConnectionProtocol;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use azalea_protocol::packets::game::ClientboundGamePacket;
|
||||||
|
use azalea_protocol::packets::game::serverbound_client_information_packet::{ChatVisibility, ServerboundClientInformationPacket};
|
||||||
|
use azalea_protocol::packets::handshake::client_intention_packet::ClientIntentionPacket;
|
||||||
|
use azalea_protocol::packets::login::ClientboundLoginPacket;
|
||||||
|
use azalea_protocol::packets::login::serverbound_custom_query_packet::ServerboundCustomQueryPacket;
|
||||||
|
use azalea_protocol::packets::login::serverbound_hello_packet::ServerboundHelloPacket;
|
||||||
|
use azalea_protocol::packets::login::serverbound_key_packet::ServerboundKeyPacket;
|
||||||
|
use azalea_protocol::resolver::resolve_address;
|
||||||
|
use azalea_protocol::ServerAddress;
|
||||||
|
use bevy_app::{App, Plugin, Update};
|
||||||
|
use bevy_ecs::event::{EventReader, EventWriter};
|
||||||
|
use bevy_ecs::prelude::IntoSystemConfigs;
|
||||||
|
use log::{debug, info};
|
||||||
|
use regex::Regex;
|
||||||
|
use serenity::builder::ParseValue;
|
||||||
|
use serenity::http::Http;
|
||||||
|
use serenity::model::prelude::Webhook;
|
||||||
|
use tokio::select;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
use crate::bacon::handle_message;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::DiscordChatMessage;
|
||||||
|
|
||||||
|
pub async fn _main(config: &Arc<RwLock<Config>>, reciever: &mut Receiver<DiscordChatMessage>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let account = Account::microsoft(&config.read().await.relay.microsoft_email).await?;
|
||||||
|
|
||||||
|
// An event that causes the schedule to run. This is only used internally.
|
||||||
|
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins((DefaultPlugins, AutoRespawnPlugin));
|
||||||
|
|
||||||
|
let ecs_lock = start_ecs(app, run_schedule_receiver, run_schedule_sender.clone());
|
||||||
|
|
||||||
|
let (mut client, mut rx) = Client::start_client(
|
||||||
|
ecs_lock,
|
||||||
|
&account,
|
||||||
|
&ServerAddress::from(SocketAddr::new(config.read().await.relay.ip.into(), config.read().await.relay.port.clone())),
|
||||||
|
&SocketAddr::new(config.read().await.relay.ip.into(), config.read().await.relay.port.clone()),
|
||||||
|
run_schedule_sender,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
client.chat("Hello!");
|
||||||
|
let mut last_sent_chat = SystemTime::now();
|
||||||
|
|
||||||
|
let mut webhooks = vec![];
|
||||||
|
|
||||||
|
let http = Http::new("");
|
||||||
|
|
||||||
|
for webhook in &config.read().await.relay.webhooks {
|
||||||
|
let wh = Webhook::from_url(&http, webhook).await?;
|
||||||
|
|
||||||
|
webhooks.push(wh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//client.chat("Hello, I'm CuberCore! Thank god this thing is finally working");
|
||||||
|
|
||||||
|
client.set_client_information(ServerboundClientInformationPacket {
|
||||||
|
language: "en_US".to_string(),
|
||||||
|
view_distance: 16,
|
||||||
|
chat_visibility: ChatVisibility::Full,
|
||||||
|
chat_colors: true,
|
||||||
|
model_customization: Default::default(),
|
||||||
|
main_hand: Default::default(),
|
||||||
|
text_filtering_enabled: false,
|
||||||
|
allows_listing: true,
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
msg = reciever.recv() => {
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
client.chat(&format!("@{}: {}", msg.username, msg.message));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e = rx.recv() => {
|
||||||
|
if let Some(e) = e {
|
||||||
|
handle_packet(e, config, reciever, &webhooks, &mut client, &http).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_packet(e: Event, config: &Arc<RwLock<Config>>, reciever: &Receiver<DiscordChatMessage>, webhooks: &Vec<Webhook>, client: &mut Client, http: &Http) -> Result<(), Box<dyn Error>> {
|
||||||
|
match e {
|
||||||
|
Event::Init => {}
|
||||||
|
Event::Login => {}
|
||||||
|
Event::Chat(pkt) => {
|
||||||
|
if pkt.content().starts_with("/skill") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("<{} ({})> {}", pkt.username().unwrap_or("SYSTEM".to_string()), pkt.uuid().map(|u| u.to_string()).unwrap_or("SYSTEM".to_string()), pkt.message());
|
||||||
|
|
||||||
|
handle_message(pkt.clone(), client, config).await;
|
||||||
|
|
||||||
|
let re = Regex::new(r"@[a-z0-9]+:").unwrap();
|
||||||
|
if !(re.is_match(&pkt.content()) && pkt.uuid() == Some(config.read().await.relay.uuid)) {
|
||||||
|
for wh in webhooks {
|
||||||
|
wh
|
||||||
|
.execute(http, false, |w| {
|
||||||
|
w.content(pkt.content())
|
||||||
|
.username(pkt.username().unwrap_or("console".to_string())).allowed_mentions(|am| am.parse(ParseValue::Users).parse(ParseValue::Roles)).avatar_url(&format!("https://mc-heads.net/head/{}", pkt.uuid().map(|u| u.to_string()).unwrap_or("console".to_string())))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Could not execute webhook.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
if pkt.content() == "!disconnect" {
|
||||||
|
if let Some(u) = pkt.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
client.chat("Nice try. You need to be an 'admin' to do that.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.chat("Nice try. You need to be an 'admin' to do that.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
client.chat("Okay, disconnecting. Cya!");
|
||||||
|
client.disconnect();
|
||||||
|
return Err("Disconnected.".into());
|
||||||
|
}
|
||||||
|
if pkt.content() == "!reconnect" {
|
||||||
|
if let Some(u) = pkt.uuid() {
|
||||||
|
if !config.read().await.permissions.admin.uuid_members.contains(&u) {
|
||||||
|
client.chat("Nice try. You need to be an 'admin' to do that.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.chat("Nice try. You need to be an 'admin' to do that.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
client.chat("Okay, reconnecting. Cya!");
|
||||||
|
client.disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if pkt.content() == "!help" {
|
||||||
|
client.chat("Hi, I'm CuberCore! I am owned by CoreCuber/@realcore.");
|
||||||
|
client.chat("I function as the chat relay for this server and also do other useful things.");
|
||||||
|
client.chat("My commands:");
|
||||||
|
client.chat("!sleep - Sleep in the nearest bed");
|
||||||
|
client.chat("!disconnect - Disconnect from the server (admin only)");
|
||||||
|
client.chat("!reconnect - Reconnect to the server (admin only)");
|
||||||
|
client.chat("!location - List all locations I know about");
|
||||||
|
client.chat("!location <location> - Get the coords of a location");
|
||||||
|
}
|
||||||
|
if pkt.content() == "!bot" {
|
||||||
|
client.chat("[iambot]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.content().starts_with("!location") {
|
||||||
|
let content = pkt.content();
|
||||||
|
let split = content.split(' ').collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if split.len() == 1 {
|
||||||
|
client.chat(&format!("[bot response] Locations: {:?}", config.read().await.locations.keys().collect::<Vec<_>>()));
|
||||||
|
} else {
|
||||||
|
let location = split[1];
|
||||||
|
|
||||||
|
if let Some(coords) = config.read().await.locations.get(location) {
|
||||||
|
client.chat(&format!("[bot response] Location '{}' is at {}", location, coords));
|
||||||
|
} else {
|
||||||
|
client.chat(&format!("[bot response] I don't know where '{}' is.", location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
Event::Tick => {}
|
||||||
|
Event::Packet(p) => {
|
||||||
|
match p.as_ref() {
|
||||||
|
ClientboundGamePacket::Disconnect(_) => {
|
||||||
|
info!("Disconnected, restarting");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::AddPlayer(_) => {}
|
||||||
|
Event::RemovePlayer(_) => {}
|
||||||
|
Event::UpdatePlayer(_) => {}
|
||||||
|
Event::Death(e) => {}
|
||||||
|
Event::KeepAlive(_) => {}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct AutoRespawnPlugin;
|
||||||
|
|
||||||
|
impl Plugin for AutoRespawnPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Update, auto_respawn.before(perform_respawn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn auto_respawn(
|
||||||
|
mut events: EventReader<DeathEvent>,
|
||||||
|
mut perform_respawn_events: EventWriter<PerformRespawnEvent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
perform_respawn_events.send(PerformRespawnEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
let addr = SocketAddr::new(config.read().await.relay.ip.into(), config.read().await.relay.port.clone());
|
||||||
|
let mut conn = Connection::new(&addr).await?;
|
||||||
|
|
||||||
|
// handshake
|
||||||
|
conn.write(
|
||||||
|
ClientIntentionPacket {
|
||||||
|
protocol_version: config.read().await.relay.protocol_version as u32,
|
||||||
|
hostname: config.read().await.relay.ip.to_string(),
|
||||||
|
port: config.read().await.relay.port,
|
||||||
|
intention: ConnectionProtocol::Login,
|
||||||
|
}.get()
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
let mut conn = conn.login();
|
||||||
|
|
||||||
|
// login
|
||||||
|
conn.write(
|
||||||
|
ServerboundHelloPacket {
|
||||||
|
name: config.read().await.relay.username,
|
||||||
|
profile_id: Some(config.read().await.relay.uuid),
|
||||||
|
}.get()
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
let (conn, game_profile) = loop {
|
||||||
|
let packet = conn.read().await?;
|
||||||
|
|
||||||
|
match packet {
|
||||||
|
ClientboundLoginPacket::Hello(p) => {
|
||||||
|
let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap();
|
||||||
|
|
||||||
|
conn.write(
|
||||||
|
ServerboundKeyPacket {
|
||||||
|
key_bytes: e.encrypted_public_key,
|
||||||
|
encrypted_challenge: e.encrypted_nonce,
|
||||||
|
}.get()
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
conn.set_encryption_key(e.secret_key);
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientboundLoginPacket::LoginCompression(p) => {
|
||||||
|
conn.set_compression_threshold(p.compression_threshold);
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientboundLoginPacket::GameProfile(p) => {
|
||||||
|
break (conn.game(), p.game_profile);
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientboundLoginPacket::LoginDisconnect(p) => {
|
||||||
|
eprintln!("login disconnect: {}", p.reason);
|
||||||
|
return Err("login disconnect".into());
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientboundLoginPacket::CustomQuery(p) => {
|
||||||
|
if p.identifier == ResourceLocation::new("liveoverflowmod:join")? {
|
||||||
|
let mut cursor: Cursor<&[u8]> = Cursor::new(&p.data.0);
|
||||||
|
let sum_of = String::read_from(&mut cursor)?;
|
||||||
|
let a = u32::var_read_from(&mut cursor)?;
|
||||||
|
let b = u32::var_read_from(&mut cursor)?;
|
||||||
|
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
a.var_write_into(&mut buf)?;
|
||||||
|
b.var_write_into(&mut buf)?;
|
||||||
|
|
||||||
|
conn.write(ServerboundCustomQueryPacket {
|
||||||
|
transaction_id: p.transaction_id,
|
||||||
|
data: Some(buf.into_inner().into()),
|
||||||
|
}.get()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub async fn azalea_main(config: Arc<RwLock<Config>>, mut reciever: Receiver<DiscordChatMessage>) {
|
||||||
|
loop {
|
||||||
|
match _main(&config, &mut reciever).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
panic!("bot thread exited with an error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +1,61 @@
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use log::{Level, LevelFilter};
|
use log::{Level, LevelFilter};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub bot: BotConfig,
|
pub bot: BotConfig,
|
||||||
pub permissions: PermissionsGroups,
|
pub permissions: PermissionsGroups,
|
||||||
pub relay: RelayConfig,
|
pub relay: RelayConfig,
|
||||||
pub logging: LoggingConfig
|
pub logging: LoggingConfig,
|
||||||
|
pub locations: HashMap<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct BotConfig {
|
pub struct BotConfig {
|
||||||
pub discord_token: String,
|
pub discord_token: String,
|
||||||
pub prefix: String
|
pub prefix: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RelayConfig {
|
pub struct RelayConfig {
|
||||||
pub ip: Ipv4Addr,
|
pub ip: Ipv4Addr,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub channels: Vec<u64>,
|
pub channels: Vec<u64>,
|
||||||
pub webhooks: Vec<String>
|
pub webhooks: Vec<String>,
|
||||||
|
pub protocol_version: u64,
|
||||||
|
pub username: String,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub microsoft_email: String,
|
||||||
|
pub owner_username: String,
|
||||||
|
pub owner_uuid_undashed: String,
|
||||||
|
pub ignored: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct PermissionsGroups {
|
pub struct PermissionsGroups {
|
||||||
pub owner: PermissionGroup,
|
pub owner: PermissionGroup,
|
||||||
pub admin: PermissionGroup
|
pub admin: PermissionGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct PermissionGroup {
|
pub struct PermissionGroup {
|
||||||
pub user_members: Vec<u64>,
|
pub user_members: Vec<u64>,
|
||||||
pub group_members: Vec<u64>
|
pub group_members: Vec<u64>,
|
||||||
|
pub uuid_members: Vec<Uuid>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct LoggingConfig {
|
pub struct LoggingConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub level: LogLevel
|
pub level: LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
Debug,
|
Debug,
|
||||||
|
@ -98,4 +109,36 @@ pub fn load_config() -> Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
config
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(config: &Config) {
|
||||||
|
let mut args = env::args();
|
||||||
|
|
||||||
|
if args.len() != 2 {
|
||||||
|
eprintln!("usage: discord_bot <config path>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_path = args.nth(1).unwrap();
|
||||||
|
if !Path::new(&config_path).exists() {
|
||||||
|
eprintln!("err: config file does not exist");
|
||||||
|
eprintln!("usage: discord_bot <config path>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = match toml::to_string(&config) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error saving config: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match fs::write(config_path, config) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error saving config: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
57
src/main.rs
57
src/main.rs
|
@ -3,24 +3,29 @@ use std::{env, fs};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serenity::async_trait;
|
use serenity::async_trait;
|
||||||
use serenity::builder::ParseValue;
|
use serenity::builder::ParseValue;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
use serenity::model::channel::Message;
|
use serenity::model::channel::{Message, Reaction, ReactionType};
|
||||||
use serenity::framework::standard::macros::{command, group, hook};
|
use serenity::framework::standard::macros::{command, group, hook};
|
||||||
use serenity::framework::standard::{StandardFramework, CommandResult, Args};
|
use serenity::framework::standard::{StandardFramework, CommandResult, Args};
|
||||||
use serenity::http::Http;
|
use serenity::http::Http;
|
||||||
use serenity::model::prelude::Webhook;
|
use serenity::model::prelude::Webhook;
|
||||||
|
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
use crate::bot::azalea_main;
|
||||||
use crate::config::{Config, load_config};
|
use crate::config::{Config, load_config};
|
||||||
use crate::util::ping_channel;
|
use crate::util::ping_channel;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
pub mod bot;
|
||||||
|
pub mod bacon;
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
#[commands(ping, relay_channel, reload_config, webhook_test, botstatus)]
|
#[commands(ping, relay_channel, reload_config, webhook_test)]
|
||||||
struct General;
|
struct General;
|
||||||
|
|
||||||
struct Handler;
|
struct Handler;
|
||||||
|
@ -34,18 +39,49 @@ impl TypeMapKey for ConfigLock {
|
||||||
type Value = Arc<RwLock<Config>>;
|
type Value = Arc<RwLock<Config>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MessageChannel;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DiscordChatMessage {
|
||||||
|
pub username: String,
|
||||||
|
pub message: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeMapKey for MessageChannel {
|
||||||
|
type Value = Sender<DiscordChatMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let config = load_config();
|
let config = load_config();
|
||||||
|
|
||||||
|
if config.logging.enabled {
|
||||||
|
simple_logger::init_with_level(config.logging.level.clone().into()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = Arc::new(RwLock::new(config));
|
||||||
|
|
||||||
|
let channel: (Sender<DiscordChatMessage>, Receiver<DiscordChatMessage>) = channel(1024);
|
||||||
|
|
||||||
|
// start the bot thread
|
||||||
|
let bot_thread = tokio::spawn(discord_main(config.clone(), channel.0));
|
||||||
|
|
||||||
|
azalea_main(config, channel.1).await;
|
||||||
|
|
||||||
|
bot_thread.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn discord_main(config: Arc<RwLock<Config>>, sender: Sender<DiscordChatMessage>) {
|
||||||
|
let prefix = config.read().await.bot.prefix.clone();
|
||||||
|
|
||||||
let framework = StandardFramework::new()
|
let framework = StandardFramework::new()
|
||||||
.configure(|c| c.prefix(config.bot.prefix.clone())) // set the bot's prefix to "~"
|
.configure(|c| c.prefix(prefix.clone())) // set the bot's prefix to "~"
|
||||||
.group(&GENERAL_GROUP)
|
.group(&GENERAL_GROUP)
|
||||||
.normal_message(normal_message);
|
.normal_message(normal_message);
|
||||||
|
|
||||||
// Login with a bot token from the environment
|
// Login with a bot token from the environment
|
||||||
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
|
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
|
||||||
let mut client = Client::builder(config.bot.discord_token.clone(), intents)
|
let mut client = Client::builder(config.read().await.bot.discord_token.clone(), intents)
|
||||||
.event_handler(Handler)
|
.event_handler(Handler)
|
||||||
.framework(framework)
|
.framework(framework)
|
||||||
.await
|
.await
|
||||||
|
@ -55,7 +91,8 @@ async fn main() {
|
||||||
// Open the data lock in write mode, so keys can be inserted to it.
|
// Open the data lock in write mode, so keys can be inserted to it.
|
||||||
let mut data = client.data.write().await;
|
let mut data = client.data.write().await;
|
||||||
|
|
||||||
data.insert::<ConfigLock>(Arc::new(RwLock::new(config)));
|
data.insert::<ConfigLock>(config);
|
||||||
|
data.insert::<MessageChannel>(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
// start listening for events by starting a single shard
|
// start listening for events by starting a single shard
|
||||||
|
@ -66,7 +103,7 @@ async fn main() {
|
||||||
|
|
||||||
#[hook]
|
#[hook]
|
||||||
async fn normal_message(ctx: &Context, msg: &Message) {
|
async fn normal_message(ctx: &Context, msg: &Message) {
|
||||||
let config = {
|
let (config, tx) = {
|
||||||
// While data is a RwLock, it's recommended that you always open the lock as read.
|
// While data is a RwLock, it's recommended that you always open the lock as read.
|
||||||
// This is mainly done to avoid Deadlocks for having a possible writer waiting for multiple
|
// This is mainly done to avoid Deadlocks for having a possible writer waiting for multiple
|
||||||
// readers to close.
|
// readers to close.
|
||||||
|
@ -76,12 +113,16 @@ async fn normal_message(ctx: &Context, msg: &Message) {
|
||||||
// data, instead the reference is cloned.
|
// data, instead the reference is cloned.
|
||||||
// We wrap every value on in an Arc, as to keep the data lock open for the least time possible,
|
// We wrap every value on in an Arc, as to keep the data lock open for the least time possible,
|
||||||
// to again, avoid deadlocking it.
|
// to again, avoid deadlocking it.
|
||||||
data_read.get::<ConfigLock>().expect("Expected ConfigLock in TypeMap.").clone()
|
(data_read.get::<ConfigLock>().expect("Expected ConfigLock in TypeMap.").clone(),
|
||||||
|
data_read.get::<MessageChannel>().expect("Expected MessageChannel in TypeMap.").clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
if !config.read().await.relay.channels.contains(msg.channel_id.as_u64()) { return; }
|
if !config.read().await.relay.channels.contains(msg.channel_id.as_u64()) { return; }
|
||||||
|
|
||||||
msg.reply(ctx, "TEST: msg echo ".to_string() + &msg.content).await.unwrap();
|
tx.send(DiscordChatMessage {
|
||||||
|
username: msg.author.name.clone(),
|
||||||
|
message: msg.content.clone(),
|
||||||
|
}).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
|
Loading…
Reference in New Issue