Compare commits
10 Commits
6fd6945a5f
...
431baae8c4
Author | SHA1 | Date |
---|---|---|
c0repwn3r | 431baae8c4 | |
core | 52bc824027 | |
core | 54f2c3e5fa | |
core | 2ce2aa475a | |
core | 8d73f281d2 | |
core | 3e6285c076 | |
core | 48a1a69575 | |
core | 35574fa76a | |
core | e8a60c7f2d | |
core | 304d37ded7 |
|
@ -586,10 +586,26 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.23.4",
|
"tokio-rustls 0.23.4",
|
||||||
"tungstenite",
|
"tungstenite 0.17.3",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-tungstenite"
|
||||||
|
version = "0.22.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58"
|
||||||
|
dependencies = [
|
||||||
|
"futures-io",
|
||||||
|
"futures-util",
|
||||||
|
"log 0.4.19",
|
||||||
|
"native-tls",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tungstenite 0.19.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -924,6 +940,7 @@ name = "azalea-worker"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"async-tungstenite 0.22.2",
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
"azalea-client",
|
"azalea-client",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
@ -932,6 +949,7 @@ dependencies = [
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy_app",
|
"bevy_app",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
"log 0.4.19",
|
"log 0.4.19",
|
||||||
|
@ -946,7 +964,6 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
"url 2.4.0",
|
"url 2.4.0",
|
||||||
"uuid",
|
"uuid",
|
||||||
"websocket",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5205,7 +5222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d007dc45584ecc47e791f2a9a7cf17bf98ac386728106f111159c846d624be3f"
|
checksum = "d007dc45584ecc47e791f2a9a7cf17bf98ac386728106f111159c846d624be3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-tungstenite",
|
"async-tungstenite 0.17.2",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"bytes 1.4.0",
|
"bytes 1.4.0",
|
||||||
|
@ -6003,6 +6020,26 @@ dependencies = [
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes 1.4.0",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log 0.4.19",
|
||||||
|
"native-tls",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"url 2.4.0",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twox-hash"
|
name = "twox-hash"
|
||||||
version = "1.6.3"
|
version = "1.6.3"
|
||||||
|
|
|
@ -15,7 +15,6 @@ tokio = { version = "1", features = ["full"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
websocket = { version = "0.26", features = ["async"] }
|
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
tokio-threadpool = "0.1"
|
tokio-threadpool = "0.1"
|
||||||
bevy_ecs = "0.11"
|
bevy_ecs = "0.11"
|
||||||
|
@ -30,3 +29,5 @@ 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-core = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
azalea-buf = { 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" }
|
azalea-client = { version = "0.7", git = "https://git.e3t.cc/~core/azalea" }
|
||||||
|
async-tungstenite = { version = "0.22", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||||
|
chrono = "0.4"
|
|
@ -1,5 +1,5 @@
|
||||||
api_status_url = "http://localhost:8171/status"
|
api_status_url = "https://data-api.locationoverflow.coredoes.dev/status"
|
||||||
token = "test-rw-token"
|
token = "rw-minecraft-b1d648ab-498a-4499-b5a7-64aedcd7c836"
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
ip = "95.216.24.174"
|
ip = "95.216.24.174"
|
||||||
|
@ -15,3 +15,5 @@ ignored = []
|
||||||
[permissions]
|
[permissions]
|
||||||
owner = []
|
owner = []
|
||||||
admin = []
|
admin = []
|
||||||
|
|
||||||
|
[locations]
|
|
@ -0,0 +1,348 @@
|
||||||
|
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};
|
||||||
|
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: &Config) {
|
||||||
|
if p.username().is_none() { return; }
|
||||||
|
|
||||||
|
{
|
||||||
|
info!("{} {} {:?}", p.username().unwrap(), p.uuid().unwrap(), config.server.ignored);
|
||||||
|
|
||||||
|
if config.server.ignored.contains(&p.username().unwrap()) || config.server.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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.uuid() == Some(config.server.uuid) && components[0].starts_with('@') {
|
||||||
|
components.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() == 0 {
|
||||||
|
return; // hotfix: fuck you dam
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_for_me = components[0].starts_with(&format!("!@{}", config.server.username)) || (!components[0].starts_with("!@") && components[0].starts_with("!"));
|
||||||
|
|
||||||
|
if !is_for_me { return; }
|
||||||
|
|
||||||
|
if components[0] == format!("!@{}", config.server.username) && components.len() == 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = if components[0] == format!("!@{}", config.server.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.server.owner_username, config.server.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 not reloaded - not yet implemented");
|
||||||
|
},
|
||||||
|
"ignore" => {
|
||||||
|
if let Some(u) = p.uuid() {
|
||||||
|
if !config.permissions.admin.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !ignore [player]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.chat("Not yet implemented.");
|
||||||
|
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.permissions.admin.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() != 2 {
|
||||||
|
client.chat("[bot] usage: !unignore [player]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.chat("not yet implemented");
|
||||||
|
|
||||||
|
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.permissions.admin.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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.chat("not yet implemented");
|
||||||
|
|
||||||
|
return;
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
let has = { config.write().await.permissions.admin.contains(&player) };
|
||||||
|
if !has {
|
||||||
|
config.write().await.permissions.admin.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.permissions.admin.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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.chat("not yet implemented");
|
||||||
|
return;
|
||||||
|
/*
|
||||||
|
|
||||||
|
{
|
||||||
|
let has = { config.write().await.permissions.admin.contains(&player) };
|
||||||
|
if has {
|
||||||
|
let index = { config.write().await.permissions.admin.iter().position(|u| u == &player).unwrap() };
|
||||||
|
config.write().await.permissions.admin.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.locations.keys().collect::<Vec<_>>()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if components.len() == 2 {
|
||||||
|
if let Some(pos) = config.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.permissions.admin.contains(&u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// !location abode "1 2 3"
|
||||||
|
// !location abode remove
|
||||||
|
|
||||||
|
if components[2] == "remove" {
|
||||||
|
client.chat("not yet implemented");
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
config.write().await.locations.remove(&components[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_config(&*config.read().await);
|
||||||
|
|
||||||
|
client.chat(&format!("Okay, I removed the {} location.", components[1]));
|
||||||
|
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.chat("not yet implemented");
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -8,7 +9,8 @@ pub struct Config {
|
||||||
pub api_status_url: Url,
|
pub api_status_url: Url,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub server: ServerConfig,
|
pub server: ServerConfig,
|
||||||
pub permissions: PermissionsConfig
|
pub permissions: PermissionsConfig,
|
||||||
|
pub locations: HashMap<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
|
@ -1,29 +1,41 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs;
|
use std::{fs, thread};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use azalea_client::{Account, Client, DefaultPlugins, start_ecs};
|
use std::sync::Arc;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use async_tungstenite::tokio::{connect_async, ConnectStream};
|
||||||
|
use async_tungstenite::tungstenite::Message;
|
||||||
|
use async_tungstenite::WebSocketStream;
|
||||||
|
use azalea_client::{Account, Client, DefaultPlugins, Event, start_ecs};
|
||||||
use azalea_client::packet_handling::DeathEvent;
|
use azalea_client::packet_handling::DeathEvent;
|
||||||
use azalea_client::respawn::{perform_respawn, PerformRespawnEvent};
|
use azalea_client::respawn::{perform_respawn, PerformRespawnEvent};
|
||||||
|
use azalea_protocol::packets::game::ClientboundGamePacket;
|
||||||
use azalea_protocol::packets::game::serverbound_client_information_packet::{ChatVisibility, ServerboundClientInformationPacket};
|
use azalea_protocol::packets::game::serverbound_client_information_packet::{ChatVisibility, ServerboundClientInformationPacket};
|
||||||
use azalea_protocol::ServerAddress;
|
use azalea_protocol::ServerAddress;
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_ecs::event::{EventReader, EventWriter};
|
use bevy_ecs::event::{EventReader, EventWriter};
|
||||||
use bevy_ecs::prelude::IntoSystemConfigs;
|
use bevy_ecs::prelude::IntoSystemConfigs;
|
||||||
use log::{debug, error, info};
|
use chrono::DateTime;
|
||||||
|
use futures::{SinkExt, Stream, StreamExt};
|
||||||
|
use log::{debug, error, info, Level};
|
||||||
|
use regex::Regex;
|
||||||
|
use tokio::select;
|
||||||
|
use tokio::sync::broadcast::channel;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio_threadpool::ThreadPool;
|
use tokio_threadpool::ThreadPool;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use websocket::{ClientBuilder, Message, OwnedMessage};
|
use common::message::{GatewayChatMessage, GatewayChatSource, GatewayPacketC2S, GatewayPacketS2C};
|
||||||
use websocket::websocket_base::result::WebSocketResult;
|
|
||||||
use common::message::{GatewayChatMessage, GatewayPacketC2S, GatewayPacketS2C};
|
|
||||||
use common::status::{DATA_API_VERSION, Status};
|
use common::status::{DATA_API_VERSION, Status};
|
||||||
|
use crate::bacon::handle_message;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
|
pub mod bacon;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
simple_logger::init_with_env().unwrap();
|
simple_logger::init_with_level(Level::Info).unwrap();
|
||||||
|
|
||||||
info!("Loading config");
|
info!("Loading config");
|
||||||
|
|
||||||
|
@ -43,7 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
info!("Loading status from the API ({})...", config.api_status_url);
|
info!("Loading status from the API ({})...", config.api_status_url);
|
||||||
|
|
||||||
let status: Status = reqwest::get(config.api_status_url).await?.json().await?;
|
let status: Status = reqwest::get(config.api_status_url.clone()).await?.json().await?;
|
||||||
|
|
||||||
debug!("{:?}", status);
|
debug!("{:?}", status);
|
||||||
|
|
||||||
|
@ -54,18 +66,66 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
info!("Connecting to gateway uri {}", status.gateway_url.to_string());
|
info!("Connecting to gateway uri {}", status.gateway_url.to_string());
|
||||||
|
|
||||||
let mut client = ClientBuilder::new(status.gateway_url.as_str())?.connect(None)?;
|
let mut gateway_uri = status.gateway_url;
|
||||||
|
if gateway_uri.scheme() == "http" {
|
||||||
|
gateway_uri.set_scheme("ws").unwrap();
|
||||||
|
} else if gateway_uri.scheme() == "https" {
|
||||||
|
gateway_uri.set_scheme("wss").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// send the authentication packet, and then start listen
|
let (mut ws_stream, _) = connect_async(&gateway_uri).await?;
|
||||||
|
|
||||||
let message = Message::text(serde_json::to_string(&GatewayPacketC2S::Authenticate {
|
debug!("websocket connection opened - sending Authenticate");
|
||||||
|
|
||||||
|
ws_stream.send(Message::text(serde_json::to_string(&GatewayPacketC2S::Authenticate {
|
||||||
token: config.token.clone(),
|
token: config.token.clone(),
|
||||||
request_write_perms: true,
|
request_write_perms: true, // we *need* write perms for the relay
|
||||||
})?);
|
})?)).await?;
|
||||||
|
|
||||||
client.send_message(&message)?;
|
debug!("starting handshake loop (waiting for AuthenticationAccepted)");
|
||||||
|
|
||||||
//let (mut tx, mut rx) = client.split().unwrap();
|
loop {
|
||||||
|
let msg = ws_stream.next().await;
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Some(msg) => match msg? {
|
||||||
|
Message::Text(packet) => {
|
||||||
|
debug!("recv gateway packet {}", packet);
|
||||||
|
let packet: GatewayPacketS2C = serde_json::from_str(&packet)?;
|
||||||
|
|
||||||
|
match packet {
|
||||||
|
GatewayPacketS2C::AuthenticationAccepted { write_perms } => {
|
||||||
|
info!("Gateway accepted our token.");
|
||||||
|
|
||||||
|
if !write_perms {
|
||||||
|
error!("Gateway server did not grant us write perms to the chat stream socket.");
|
||||||
|
error!("This is REQUIRED for azalea-worker to function correctly.");
|
||||||
|
error!("Check to ensure your token has `allow-write = true` set in the API config file.");
|
||||||
|
error!("AuthenticationAccepted with write_perms:false");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
GatewayPacketS2C::Disconnect { reason } => {
|
||||||
|
error!("Disconnected by server: {:?}", reason);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
GatewayPacketS2C::Relayed { .. } => {} // ignore messages for now
|
||||||
|
}
|
||||||
|
},
|
||||||
|
msg => {
|
||||||
|
debug!("unknown message {:?}", msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
debug!("didn't receive anything");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Connected to gateway, starting the minecraft client");
|
||||||
|
|
||||||
info!("Connecting to the Minecraft server...");
|
info!("Connecting to the Minecraft server...");
|
||||||
|
|
||||||
|
@ -99,6 +159,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
allows_listing: true,
|
allows_listing: true,
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
msg = ws_stream.next() => handle_ws_message(msg, &mut client).await?,
|
||||||
|
e = rx.recv() => {
|
||||||
|
if let Some(e) = e {
|
||||||
|
handle_packet(e, &config, &mut client, &mut ws_stream).await?
|
||||||
|
} else {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
for msg in client.incoming_messages() {
|
for msg in client.incoming_messages() {
|
||||||
match msg {
|
match msg {
|
||||||
|
@ -142,6 +215,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,3 +238,146 @@ fn auto_respawn(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_ws_message(msg: Option<Result<Message, async_tungstenite::tungstenite::Error>>, client: &mut Client) -> Result<(), Box<dyn Error>> {
|
||||||
|
match msg {
|
||||||
|
Some(msg) => match msg? {
|
||||||
|
Message::Text(packet) => {
|
||||||
|
debug!("recv gateway packet {}", packet);
|
||||||
|
let packet: GatewayPacketS2C = serde_json::from_str(&packet)?;
|
||||||
|
|
||||||
|
match packet {
|
||||||
|
GatewayPacketS2C::AuthenticationAccepted { .. } => {
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
GatewayPacketS2C::Disconnect { reason } => {
|
||||||
|
error!("Disconnected by server: {:?}", reason);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
GatewayPacketS2C::Relayed { msg } => {
|
||||||
|
if matches!(msg.source, GatewayChatSource::Minecraft) { return Ok(()); }
|
||||||
|
|
||||||
|
client.chat(&format!("{}: {}", msg.username, msg.message));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
msg => {
|
||||||
|
debug!("unknown message {:?}", msg);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
debug!("didn't receive anything");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_packet(e: Event, config: &Config, client: &mut Client, ws_stream: &mut WebSocketStream<ConnectStream>) -> Result<(), Box<dyn Error>> {
|
||||||
|
match e {
|
||||||
|
Event::Init => {}
|
||||||
|
Event::Login => {}
|
||||||
|
Event::Chat(pkt) => {
|
||||||
|
if !pkt.content().starts_with("/skill") {
|
||||||
|
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.server.uuid)) {
|
||||||
|
info!("sending message");
|
||||||
|
ws_stream.send(Message::Text(serde_json::to_string(&GatewayPacketC2S::Relay { msg: GatewayChatMessage {
|
||||||
|
source: GatewayChatSource::Minecraft,
|
||||||
|
username: pkt.username().unwrap_or("SYSTEM".to_string()),
|
||||||
|
message: pkt.content(),
|
||||||
|
timestamp: DateTime::from(SystemTime::now()),
|
||||||
|
}})?)).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
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 Err("Disconnected".into());
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::AddPlayer(_) => {}
|
||||||
|
Event::RemovePlayer(_) => {}
|
||||||
|
Event::UpdatePlayer(_) => {}
|
||||||
|
Event::Death(e) => {}
|
||||||
|
Event::KeepAlive(_) => {}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ use url::Url;
|
||||||
use websocket::{ClientBuilder, Message, OwnedMessage, WebSocketError};
|
use websocket::{ClientBuilder, Message, OwnedMessage, WebSocketError};
|
||||||
use websocket::websocket_base::result::WebSocketResult;
|
use websocket::websocket_base::result::WebSocketResult;
|
||||||
use common::message::{GatewayChatMessage, GatewayPacketC2S, GatewayPacketS2C};
|
use common::message::{GatewayChatMessage, GatewayPacketC2S, GatewayPacketS2C};
|
||||||
|
use common::message::GatewayChatSource::Discord;
|
||||||
use common::status::{DATA_API_VERSION, Status};
|
use common::status::{DATA_API_VERSION, Status};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
|
@ -79,7 +80,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
return Err("Disconnected by server")?;
|
return Err("Disconnected by server")?;
|
||||||
}
|
}
|
||||||
GatewayPacketS2C::Relayed { msg } => {
|
GatewayPacketS2C::Relayed { msg } => {
|
||||||
|
if msg.message.starts_with("/skill") { continue; } // fix chatspam problem
|
||||||
info!("msg: {:?}", msg);
|
info!("msg: {:?}", msg);
|
||||||
|
if matches!(msg.source, Discord) { continue; }
|
||||||
for wh in &config.websockets {
|
for wh in &config.websockets {
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let wh_clone = wh.clone();
|
let wh_clone = wh.clone();
|
||||||
|
@ -114,11 +117,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
async fn send_webhook(msg: GatewayChatMessage, webhook: String) {
|
async fn send_webhook(msg: GatewayChatMessage, webhook: String) {
|
||||||
let http = Http::new("");
|
let http = Http::new("");
|
||||||
let wh = Webhook::from_url(&http, &webhook).await.unwrap();
|
let wh = Webhook::from_url(&http, &webhook).await.unwrap();
|
||||||
|
|
||||||
wh.execute(&http, false, |w| {
|
wh.execute(&http, false, |w| {
|
||||||
w.content(msg.message)
|
w.content(msg.message)
|
||||||
.username(&msg.username)
|
.username(&msg.username)
|
||||||
.allowed_mentions(|am| am.parse(ParseValue::Users).parse(ParseValue::Roles))
|
.allowed_mentions(|am| am.parse(ParseValue::Users).parse(ParseValue::Roles))
|
||||||
.avatar_url(&format!("https://mc-heads.net/head/{}", msg.username))
|
.avatar_url(&format!("https://mc-heads.net/head/{}", msg.username.split(' ').next().unwrap()))
|
||||||
}).await.unwrap();
|
}).await.unwrap();
|
||||||
debug!("send job complete");
|
debug!("send job complete");
|
||||||
}
|
}
|
Loading…
Reference in New Issue