Skip to content

Commit

Permalink
0.7.0 - DiscordTrackerPlugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ShayBox committed Nov 14, 2024
1 parent 6495456 commit 5b997a8
Show file tree
Hide file tree
Showing 26 changed files with 778 additions and 546 deletions.
242 changes: 144 additions & 98 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shaysbot"
version = "0.6.5"
version = "0.7.0"
authors = ["Shayne Hartford <shaybox@shaybox.com>"]
edition = "2021"
description = "My personal Minecraft bot using Azalea"
Expand All @@ -23,12 +23,13 @@ lazy-regex = "3"
#ncr = { path = "../ncr-rs", features = ["cfb8", "ecb", "gcm"] }
ncr = { git = "https://github.com/Shays-Forks/ncr-rs.git", features = ["cfb8", "ecb", "gcm"] }
num-traits = "0.2"
parking_lot = "0.12"
semver = "1"
serde = "1"
serde_with = "3"
serenity = "0.12"
smart-default = "0.7"
structstruck = "0.4"
strum = { version = "0.26", features = ["derive"] }
terminal-link = "0.1"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ No Chat Reports (NCR) encryption to ensure secure and private communication.
- [**AutoLook**](src/plugins/auto_look.rs) - Automatically looks at specific targets
- [**AutoPearl**](src/plugins/auto_pearl.rs) - Handles automatic ender pearl throwing
- [**AutoTotem**](src/plugins/auto_totem.rs) - Automatically equips totems for survival
- [**DiscordTracker**](src/plugins/pearl_tracker.rs) - Tracks visual range events like players and shulkers
- [**PearlTracker**](src/plugins/pearl_tracker.rs) - Tracks and manages ender pearl cooldowns and usage

### Commands

- [**Pearl**](src/plugins/commands/pearl.rs) - Manages ender pearl-related commands and tracking
- [**Playtime**](src/plugins/commands/playtime.rs) - Tracks and displays player playtime statistics (2b2t.vc)
- [**Seen**](src/plugins/commands/seen.rs) - Shows when players were last seen online (2b2t.vc)
- [**Pearl**](src/commands/pearl.rs) - Manages ender pearl-related commands and tracking
- [**Playtime**](src/commands/playtime.rs) - Tracks and displays player playtime statistics (2b2t.vc)
- [**Seen**](src/commands/seen.rs) - Shows when players were last seen online (2b2t.vc)
- [**Whitelist**](src/commands/whitelist.rs) - Whitelist and link Minecraft and Discord accounts
4 changes: 2 additions & 2 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
[toolchain] # Failing to build
channel = "nightly-2024-11-11"
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::VecDeque;

use azalea::{
app::{App, Plugin, Update},
ecs::prelude::*,
Expand All @@ -7,7 +9,7 @@ use bevy_discord::{bot::events::BMessage, http::DiscordHttpResource, runtime::to
use serenity::json::json;

use crate::{
plugins::commands::{CommandEvent, CommandSender, CommandSource, Registry, WhisperEvent},
commands::{CommandEvent, CommandSender, CommandSource, Commands, WhisperEvent},
settings::Settings,
};

Expand All @@ -23,7 +25,6 @@ pub fn handle_message_event(
mut command_events: EventWriter<CommandEvent>,
mut message_events: EventReader<BMessage>,
mut query: Query<Entity, (With<Player>, With<LocalEntity>)>,
registry: Res<Registry>,
settings: Res<Settings>,
) {
for event in message_events.read() {
Expand All @@ -32,13 +33,7 @@ pub fn handle_message_event(
};

let message = event.new_message.clone();
let Some((args, command)) =
registry.find_command(&message.content, &settings.command_prefix)
else {
continue;
};

// Check if whitelist is enabled and if the user is whitelisted.
if !settings.whitelist.is_empty()
&& !settings
.whitelist
Expand All @@ -51,8 +46,9 @@ pub fn handle_message_event(
let user_id = message.author.id.to_string();
tokio_runtime().spawn(async move {
let content = [
String::from("[404] Your Discord account isn't linked to a Minecraft account."),
format!("Message the bot in-game '{prefix}whitelist link {user_id}' to link."),
String::from("[404] Your Discord and Minecraft accounts aren't linked,"),
format!("In Game: `{prefix}whitelist link {user_id}`"),
format!("Discord: `{prefix}whitelist link (auth.aristois.net)`"),
];

let map = json!({
Expand All @@ -67,10 +63,19 @@ pub fn handle_message_event(
continue;
}

let mut args = message.content.split(' ').collect::<VecDeque<_>>();
let Some(alias) = args.pop_front() else {
continue; /* Command Missing */
};

let Some(command) = Commands::find(alias) else {
continue; /* Command Invalid */
};

command_events.send(CommandEvent {
entity,
args,
command: *command,
args: args.into_iter().map(String::from).collect(),
command,
source: CommandSource::Discord(message.channel_id),
sender: CommandSender::Discord(message.author.id),
});
Expand Down
139 changes: 139 additions & 0 deletions src/commands/handlers/minecraft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use std::collections::VecDeque;

use azalea::{
app::{App, Plugin, Update},
chat::{handle_send_chat_event, ChatPacketKind, ChatReceivedEvent, SendChatKindEvent},
ecs::prelude::*,
TabList,
};
use ncr::AesKey;

use crate::{
commands::{
handlers::Cooldown,
CommandEvent,
CommandSender,
CommandSource,
Commands,
WhisperEvent,
},
encryption::{find_encryption, try_encrypt, KEY},
settings::Settings,
};

pub struct MinecraftCommandsPlugin;

impl Plugin for MinecraftCommandsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(Cooldown::default())
.add_event::<CommandEvent>()
.add_event::<WhisperEvent>()
.add_systems(
Update,
(
handle_chat_received_event,
handle_minecraft_whisper_event.before(handle_send_chat_event),
)
.chain(),
);
}
}

pub fn handle_chat_received_event(
mut events: EventReader<ChatReceivedEvent>,
mut command_events: EventWriter<CommandEvent>,
mut cooldown: ResMut<Cooldown>,
query: Query<&TabList>,
settings: Res<Settings>,
) {
let Ok(tab_list) = query.get_single() else {
return;
};

for event in events.read() {
let (username, content) = event.packet.split_sender_and_content();
let (username, content) = if let Some(username) = username {
(username, content) /* Vanilla Server Format */
} else if let Some((_whole, username, content)) = regex_captures!(
r"^(?:\[.+\] )?([a-zA-Z_0-9]{1,16}) (?:> )?(?:whispers: |-> )?(.+)$",
&content
) {
(username.to_string(), content.to_string())
} else {
continue;
};

let Some((uuid, _)) = tab_list.iter().find(|(_, i)| i.profile.name == username) else {
continue;
};

if !settings.whitelist.is_empty() && !settings.whitelist.contains_key(uuid) {
continue;
}

let key = AesKey::decode_base64(&settings.encryption.key).unwrap_or_else(|_| KEY.clone());
let (encryption, content) = find_encryption(&content, &key);

let mut args = content.split(' ').collect::<VecDeque<_>>();
let Some(alias) = args.pop_front() else {
continue; /* Command Missing */
};

let Some(command) = Commands::find(&alias.replace(&settings.command_prefix, "")) else {
continue; /* Command Invalid */
};

if cooldown.check(&username, settings.command_cooldown) {
continue; /* Command Cooldown */
}

command_events.send(CommandEvent {
entity: event.entity,
args: args.into_iter().map(String::from).collect(),
command,
sender: CommandSender::Minecraft(*uuid),
source: CommandSource::Minecraft(encryption),
});
}
}

pub fn handle_minecraft_whisper_event(
mut chat_kind_events: EventWriter<SendChatKindEvent>,
mut whisper_events: EventReader<WhisperEvent>,
query: Query<&TabList>,
settings: Res<Settings>,
) {
let Ok(tab_list) = query.get_single() else {
return;
};

if settings.disable_responses {
return;
}

for mut event in whisper_events.read().cloned() {
#[rustfmt::skip]
let (
CommandSource::Minecraft(type_encryption),
CommandSender::Minecraft(uuid)
) = (event.source, event.sender) else {
continue;
};

let Some(username) = tab_list
.iter()
.find(|(_, info)| info.profile.uuid == uuid)
.map(|(_, info)| info.profile.name.clone())
else {
continue; /* Player Offline */
};

try_encrypt(&mut event.content, &settings.encryption, type_encryption);

chat_kind_events.send(SendChatKindEvent {
entity: event.entity,
kind: ChatPacketKind::Command,
content: format!("w {username} {}", event.content),
});
}
}
28 changes: 28 additions & 0 deletions src/commands/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{
collections::HashMap,
time::{Duration, Instant},
};

use azalea::{ecs::prelude::*, prelude::*};

pub mod prelude;

mod discord;
mod minecraft;

#[derive(Default, Resource)]
pub struct Cooldown(HashMap<String, Instant>);

impl Cooldown {
fn check(&mut self, sender: &str, duration: Duration) -> bool {
if let Some(instant) = self.0.get(sender) {
if instant.elapsed() < duration {
return true;
}
} else {
self.0.insert(sender.to_owned(), Instant::now());
}

false
}
}
File renamed without changes.
69 changes: 69 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pub mod handlers;
pub mod prelude;

mod pearl;
mod playtime;
mod seen;
mod whitelist;

use std::collections::VecDeque;

use azalea::{ecs::prelude::*, prelude::*};
use serenity::all::{ChannelId, UserId};
use strum::IntoEnumIterator;
use uuid::Uuid;

use crate::{commands::prelude::*, encryption::EncryptionType};

pub trait Command {
fn aliases(&self) -> Vec<&'static str>;
}

/// Compile time checked list of commands
#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter)]
pub enum Commands {
Pearl(PearlCommandPlugin),
Playtime(PlaytimeCommandPlugin),
Seen(SeenCommandPlugin),
Whitelist(WhitelistCommandPlugin),
}

impl Commands {
fn find(alias: &str) -> Option<Self> {
Self::iter().find(|cmds| match cmds {
Self::Pearl(cmd) => cmd.aliases().contains(&alias),
Self::Playtime(cmd) => cmd.aliases().contains(&alias),
Self::Seen(cmd) => cmd.aliases().contains(&alias),
Self::Whitelist(cmd) => cmd.aliases().contains(&alias),
})
}
}

#[derive(Clone, Copy, Debug)]
pub enum CommandSender {
Discord(UserId),
Minecraft(Uuid),
}

#[derive(Clone, Copy, Debug)]
pub enum CommandSource {
Discord(ChannelId),
Minecraft(Option<EncryptionType>),
}

#[derive(Clone, Debug, Event)]
pub struct CommandEvent {
pub entity: Entity,
pub args: VecDeque<String>,
pub command: Commands,
pub sender: CommandSender,
pub source: CommandSource,
}

#[derive(Clone, Debug, Event)]
pub struct WhisperEvent {
pub entity: Entity,
pub content: String,
pub sender: CommandSender,
pub source: CommandSource,
}
Loading

0 comments on commit 5b997a8

Please sign in to comment.