diff --git a/.idea/locationoverflow.iml b/.idea/locationoverflow.iml
index 28c9e2d..7c69ed4 100644
--- a/.idea/locationoverflow.iml
+++ b/.idea/locationoverflow.iml
@@ -5,6 +5,7 @@
+
diff --git a/Cargo.lock b/Cargo.lock
index d0b3d07..45d1187 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2450,6 +2450,26 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "discord-worker"
+version = "0.1.0"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "common",
+ "log 0.4.19",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serenity",
+ "simple_logger",
+ "tokio",
+ "tokio-threadpool",
+ "toml",
+ "url 2.4.0",
+ "websocket",
+]
+
[[package]]
name = "dispatch"
version = "0.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index 1161903..e8f3863 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,5 +3,6 @@ members = [
"api",
"common",
"websocket-worker",
- "azalea-worker"
+ "azalea-worker",
+ "discord-worker"
]
\ No newline at end of file
diff --git a/discord-worker/Cargo.toml b/discord-worker/Cargo.toml
new file mode 100644
index 0000000..7295ec2
--- /dev/null
+++ b/discord-worker/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "discord-worker"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+log = "0.4"
+simple_logger = "4.1"
+serde = { version = "1", features = ["derive"] }
+toml = "0.7"
+url = { version = "2.3", features = ["serde"] }
+tokio = { version = "1", features = ["full"] }
+reqwest = { version = "0.11", features = ["json"] }
+serde_json = "1"
+async-trait = "0.1"
+websocket = { version = "0.26", features = ["async"], no-default-features = true }
+common = { path = "../common" }
+serenity = "0.11"
+tokio-threadpool = "0.1"
+chrono = "0.4"
\ No newline at end of file
diff --git a/discord-worker/config.toml b/discord-worker/config.toml
new file mode 100644
index 0000000..03e42fc
--- /dev/null
+++ b/discord-worker/config.toml
@@ -0,0 +1,5 @@
+api_status_url = "https://data-api.locationoverflow.coredoes.dev/status"
+api_token = "rw-minecraft-b1d648ab-498a-4499-b5a7-64aedcd7c836"
+discord_token = "NzM3MzIzNDgzMTkyNzU0MjY3.GbGBcv.BchYB4l196mToItLuHwpWKKbc2L3LedBzUuyyM"
+relay_channels = [1130340095606673458]
+prefix = "c!"
\ No newline at end of file
diff --git a/discord-worker/src/config.rs b/discord-worker/src/config.rs
new file mode 100644
index 0000000..71a2bd6
--- /dev/null
+++ b/discord-worker/src/config.rs
@@ -0,0 +1,11 @@
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Config {
+ pub api_status_url: Url,
+ pub api_token: String,
+ pub discord_token: String,
+ pub prefix: String,
+ pub relay_channels: Vec
+}
\ No newline at end of file
diff --git a/discord-worker/src/main.rs b/discord-worker/src/main.rs
new file mode 100644
index 0000000..b3a36c4
--- /dev/null
+++ b/discord-worker/src/main.rs
@@ -0,0 +1,186 @@
+use std::error::Error;
+use std::fs;
+use std::sync::Arc;
+use std::time::SystemTime;
+use chrono::DateTime;
+use log::{debug, error, info};
+use tokio_threadpool::ThreadPool;
+use url::Url;
+use websocket::{ClientBuilder, Message as WsMessage, OwnedMessage, WebSocketError, client::sync::Client as WsClient};
+use websocket::websocket_base::result::WebSocketResult;
+use common::message::{GatewayChatMessage, GatewayChatSource, GatewayPacketC2S, GatewayPacketS2C};
+use common::status::{DATA_API_VERSION, Status};
+use crate::config::Config;
+use serenity::async_trait;
+use serenity::builder::ParseValue;
+use serenity::prelude::*;
+use serenity::model::channel::{Message, Reaction, ReactionType};
+use serenity::framework::standard::macros::{command, group, hook};
+use serenity::framework::standard::{StandardFramework, CommandResult, Args};
+use serenity::http::Http;
+use serenity::model::prelude::Webhook;
+use websocket::stream::sync::NetworkStream;
+
+pub mod config;
+
+#[group]
+struct General;
+
+struct Handler;
+
+#[async_trait]
+impl EventHandler for Handler {}
+
+struct ConfigLock;
+
+impl TypeMapKey for ConfigLock {
+ type Value = Arc>;
+}
+
+struct WsLock;
+
+impl TypeMapKey for WsLock {
+ type Value = Arc>>>;
+}
+
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ simple_logger::init_with_env().unwrap();
+
+ info!("Loading config");
+
+ let mut args = std::env::args();
+ if args.len() != 2 {
+ eprintln!("usage: ./discord-worker ");
+ std::process::exit(1);
+ }
+
+ let file = args.nth(1).unwrap();
+
+ let config_str = fs::read_to_string(&file)?;
+
+ let config: Config = toml::from_str(&config_str)?;
+
+ info!("Config loaded from {}", file);
+
+ info!("Loading status from the API ({})...", config.api_status_url);
+
+ let status: Status = reqwest::get(config.api_status_url.clone()).await?.json().await?;
+
+ debug!("{:?}", status);
+
+ if status.data_api_version != DATA_API_VERSION {
+ error!("Data API is incompatible. This version of the websocket worker was compiled with Data API v{}, but your Data API is Data API v{}", DATA_API_VERSION, status.data_api_version);
+ std::process::exit(1);
+ }
+
+ info!("Connecting to gateway uri {}", status.gateway_url.to_string());
+
+ let mut client = ClientBuilder::new(status.gateway_url.clone().as_str())?.connect(None)?;
+
+ // send the authentication packet, and then start listen
+
+ let message = WsMessage::text(serde_json::to_string(&GatewayPacketC2S::Authenticate {
+ token: config.api_token.clone(),
+ request_write_perms: true,
+ })?);
+
+ client.send_message(&message)?;
+
+ for msg in client.incoming_messages() {
+ match msg {
+ Ok(msg) => {
+ match msg {
+ OwnedMessage::Text(txt) => {
+ // decode
+ debug!("{}", txt);
+
+ let packet: GatewayPacketS2C = serde_json::from_str(&txt)?;
+
+ match packet {
+ GatewayPacketS2C::AuthenticationAccepted { write_perms } => {
+ info!("auth accepted by server");
+ if !write_perms {
+ error!("Server did not grant us write permission to the gateway socket. This is required.");
+ error!("Check that you provided the proper token and the API has it read as write-allow.");
+ std::process::exit(1);
+ }
+ break;
+ }
+ GatewayPacketS2C::Disconnect { reason } => {
+ info!("disconnected by server: {:?}", reason);
+ return Err("Disconnected by server")?;
+ }
+ _ => ()
+ }
+ }
+ OwnedMessage::Close(_) => {
+ info!("closing connection");
+ return Ok(());
+ }
+ _ => {
+ debug!("ignoring unknown type");
+ }
+ }
+ }
+ Err(e) => {
+ if matches!(e, WebSocketError::NoDataAvailable) {
+ continue;
+ }
+ error!("rx error: {}", e);
+ return Err(e.into());
+ }
+ }
+ }
+
+ info!("Connected to gateway, starting the discord client");
+
+ let framework = StandardFramework::new().group(&GENERAL_GROUP).normal_message(normal_message);
+
+ let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
+ let mut d_client = Client::builder(config.discord_token.clone(), intents).event_handler(Handler).framework(framework).await?;
+
+ {
+ let mut data = d_client.data.write().await;
+ data.insert::(Arc::new(RwLock::new(config.clone())));
+ data.insert::(Arc::new(Mutex::new(client)));
+ }
+
+ if let Err(why) = d_client.start().await {
+ error!("Discord client error: {}", why);
+ }
+
+ Ok(())
+}
+
+#[hook]
+async fn normal_message(ctx: &Context, msg: &Message) {
+ let (config, ws) = {
+ let data_read = ctx.data.read().await;
+
+ (data_read.get::().unwrap().clone(), data_read.get::().unwrap().clone())
+ };
+
+ if !config.read().await.relay_channels.contains(msg.channel_id.as_u64()) { return; }
+
+ let mut content = msg.content.clone();
+
+ if !msg.attachments.is_empty() {
+ for attachment in &msg.attachments {
+ content += " ";
+ content += &attachment.url;
+ }
+ }
+
+ let message = WsMessage::text(serde_json::to_string(&GatewayPacketC2S::Relay {
+ msg: GatewayChatMessage {
+ source: GatewayChatSource::Discord,
+ username: format!("@{}", msg.author.name),
+ message: content,
+ timestamp: DateTime::from(SystemTime::now())
+ }
+ }).unwrap());
+
+ ws.lock().await.send_message(&message).unwrap();
+}
\ No newline at end of file