Discord working

This commit is contained in:
Daniel Flanagan 2024-08-05 15:08:06 -05:00
parent 2d7af6d149
commit 1914ee2ee8
9 changed files with 91 additions and 108 deletions

45
Cargo.lock generated
View file

@ -747,29 +747,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "homecloud"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"color-eyre",
"config",
"discord",
"redact",
"reqwest",
"reqwest-middleware",
"reqwest-retry",
"reqwest-tracing",
"serde",
"serde_json",
"serde_with",
"tokio",
"tracing",
"tracing-subscriber",
"urlencoding",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.1.0"
@ -2915,3 +2892,25 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "yourcloud"
version = "0.1.0"
dependencies = [
"axum",
"color-eyre",
"config",
"discord",
"redact",
"reqwest",
"reqwest-middleware",
"reqwest-retry",
"reqwest-tracing",
"serde",
"serde_json",
"serde_with",
"tokio",
"tracing",
"tracing-subscriber",
"urlencoding",
]

View file

@ -1,10 +1,9 @@
[package] [package]
name = "homecloud" name = "yourcloud"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.82"
axum = "0.7.5" axum = "0.7.5"
color-eyre = "0.6.3" color-eyre = "0.6.3"
config = "0.14.0" config = "0.14.0"

View file

@ -16,28 +16,9 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": { "git-hooks": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"gitignore": "gitignore", "gitignore": "gitignore",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
@ -45,11 +26,11 @@
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1712579741, "lastModified": 1722857853,
"narHash": "sha256-igpsH+pa6yFwYOdah3cFciCk8gw+ytniG9quf5f/q84=", "narHash": "sha256-3Zx53oz/MSIyevuWO/SumxABkrIvojnB7g9cimxkhiE=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "70f504012f0a132ac33e56988e1028d88a48855c", "rev": "06939f6b7ec4d4f465bf3132a05367cccbbf64da",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -81,11 +62,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1712608508, "lastModified": 1722630782,
"narHash": "sha256-vMZ5603yU0wxgyQeHJryOI+O61yrX2AHwY6LOFyV1gM=", "narHash": "sha256-hMyG9/WlUi0Ho9VkRrrez7SeNlDzLxalm9FwY7n/Noo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4cba8b53da471aea2ab2b0c1f30a81e7c451f4b6", "rev": "d04953086551086b44b6f3c6b7eeb26294f207da",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -97,16 +78,16 @@
}, },
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1710695816, "lastModified": 1720386169,
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "614b4613980a522ba49f0d194531beddbb7220d3", "rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.11", "ref": "nixos-24.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -116,21 +97,6 @@
"git-hooks": "git-hooks", "git-hooks": "git-hooks",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -3,7 +3,7 @@
// TODO: handle messages // TODO: handle messages
// TODO: data persistence? (sqlite? sled?) // TODO: data persistence? (sqlite? sled?)
use std::{any::Any, future}; use std::{any::Any, fmt, future};
use discord::model::{ChannelId, Event, Message, ReadyEvent}; use discord::model::{ChannelId, Event, Message, ReadyEvent};
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -13,30 +13,30 @@ const COMMAND_PREFIX: &str = "!";
struct Discord { struct Discord {
discord: discord::Discord, discord: discord::Discord,
connection: Option<Arc<Mutex<discord::Connection>>>, connection: Option<Arc<Mutex<discord::Connection>>>,
me: Option<discord::model::CurrentUser>,
} }
impl Discord { impl Discord {
pub fn try_new(conf: Arc<Config>) -> Result<Self> { pub fn try_new(conf: Arc<Config>) -> Result<Self> {
let discord = discord::Discord::from_bot_token( if let Some(bot_token) = conf.discord.as_ref().map(|d| &d.bot_token) {
conf.discord let discord = discord::Discord::from_bot_token(bot_token.expose_secret())?;
.as_ref()
.unwrap()
.bot_token
.clone()
.expose_secret(),
)?;
Ok(Self { Ok(Self {
discord, discord,
connection: None, connection: None,
me: None,
}) })
} else {
return Err(eyre!("discord configuration was empty"));
}
} }
pub async fn connect(&mut self) -> Result<ReadyEvent> { pub async fn connect(&mut self) -> Result<ReadyEvent> {
if self.connection.is_some() { if self.connection.is_some() {
return Err(anyhow::anyhow!("already connected")); return Err(eyre!("already connected"));
} }
let (connection, ready_ev) = self.discord.connect()?; let (connection, ready_ev) = self.discord.connect()?;
self.connection = Some(Arc::new(Mutex::new(connection))); self.connection = Some(Arc::new(Mutex::new(connection)));
self.me = self.discord.get_current_user().ok();
Ok(ready_ev) Ok(ready_ev)
} }
@ -195,6 +195,20 @@ impl Discord {
} }
pub fn handle_message(&self, msg: Message) -> Result<()> { pub fn handle_message(&self, msg: Message) -> Result<()> {
if let Some(me) = &self.me {
if msg.author.id == me.id {
trace!("Ignoring message from self");
}
return Ok(());
}
if msg.author.bot {
info!(
"Ignoring bot message in channel {} with content: {}",
msg.channel_id, msg.content
);
return Ok(());
}
info!( info!(
"Recieved Discord message in channel {} with content: {}", "Recieved Discord message in channel {} with content: {}",
msg.channel_id, msg.content msg.channel_id, msg.content
@ -221,6 +235,13 @@ impl Discord {
s if s.starts_with("ping") => { s if s.starts_with("ping") => {
let _ = self.send_message(msg.channel_id, "`pong`")?; let _ = self.send_message(msg.channel_id, "`pong`")?;
} }
s if s.starts_with("dm-me") => {
let dm = self.discord.create_dm(msg.author.id)?;
self.send_message(
dm.id,
format!("DM'ing you as requested via `{}dm-me", COMMAND_PREFIX).as_str(),
)?;
}
_ => {} _ => {}
} }
Ok(()) Ok(())

View file

@ -36,17 +36,10 @@ pub struct ConfigLoadResult {
const CONFIG_FILE_PATH: &str = "./conf.toml"; const CONFIG_FILE_PATH: &str = "./conf.toml";
const ENCRYPTED_CONFIG_FILE_PATH: &str = "./conf.toml.sops"; const ENCRYPTED_CONFIG_FILE_PATH: &str = "./conf.toml.sops";
const TMP_CONFIG_FILE_NAME: &str = "homecloud-conf.toml"; const TMP_CONFIG_FILE_NAME: &str = "yourcloud-conf.toml";
impl Config { impl Config {
pub fn load() -> Result<Self> { pub fn load() -> Result<Self> {
let c = Self::builder()?.build()?;
Ok(c.try_deserialize()?)
}
#[instrument]
pub fn builder() -> Result<ConfigBuilder<DefaultState>> {
info!("builder");
let mut p: PathBuf = CONFIG_FILE_PATH.into(); let mut p: PathBuf = CONFIG_FILE_PATH.into();
let mut tmp_file_to_cleanup: Option<PathBuf> = None; let mut tmp_file_to_cleanup: Option<PathBuf> = None;
if !p.exists() { if !p.exists() {
@ -62,10 +55,10 @@ impl Config {
info!("Detected encrypted config file '{}' - attempting to use `sops` to decrypt it to '{}' ", ENCRYPTED_CONFIG_FILE_PATH, tmp_file.display()); info!("Detected encrypted config file '{}' - attempting to use `sops` to decrypt it to '{}' ", ENCRYPTED_CONFIG_FILE_PATH, tmp_file.display());
let sops_result = Command::new("sops") let sops_result = Command::new("sops")
.args([ .args([
OsStr::new("--decrypt"),
&p.into_os_string(),
OsStr::new("--output"), OsStr::new("--output"),
&tmp_file.clone().into_os_string(), &tmp_file.clone().into_os_string(),
OsStr::new("--decrypt"),
OsStr::new(ENCRYPTED_CONFIG_FILE_PATH),
]) ])
.output(); .output();
p = tmp_file.clone(); p = tmp_file.clone();
@ -78,18 +71,24 @@ impl Config {
); );
} }
} }
// TODO: log whether or not we were able to load conf.toml?
let result = Ok(Self::default_builder() let c = Self::builder(p)?.build()?;
.map_err(Error::from)?
.add_source(config::File::from(p).required(false)) let result = c.try_deserialize();
.add_source(config::Environment::with_prefix("homecloud").separator("__")));
// cleanup // cleanup
if let Some(path) = tmp_file_to_cleanup { if let Some(path) = tmp_file_to_cleanup {
let _ = std::fs::remove_file(path); let _ = std::fs::remove_file(path);
} }
result Ok(result?)
}
pub fn builder(file: PathBuf) -> Result<ConfigBuilder<DefaultState>> {
Ok(Self::default_builder()
.map_err(Report::from)?
.add_source(config::File::from(file).required(false))
.add_source(config::Environment::with_prefix("yourcloud").separator("__")))
} }
pub fn default_builder() -> Result<ConfigBuilder<DefaultState>> { pub fn default_builder() -> Result<ConfigBuilder<DefaultState>> {

View file

@ -12,6 +12,7 @@ use crate::prelude::*;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
observe::setup_logging();
let conf: Arc<Config> = match Config::load() { let conf: Arc<Config> = match Config::load() {
Err(err) => { Err(err) => {
error!("Error loading configuration: {err}"); error!("Error loading configuration: {err}");
@ -19,7 +20,6 @@ async fn main() -> Result<()> {
} }
Ok(conf) => Arc::new(conf), Ok(conf) => Arc::new(conf),
}; };
observe::setup_logging(conf.clone());
debug!("Configuration: {conf:?}"); debug!("Configuration: {conf:?}");
let mut set = tokio::task::JoinSet::new(); let mut set = tokio::task::JoinSet::new();

View file

@ -1,14 +1,12 @@
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use crate::prelude::*; pub fn setup_logging() {
pub fn setup_logging(_conf: Arc<Config>) {
color_eyre::install().expect("Failed to install color_eyre"); color_eyre::install().expect("Failed to install color_eyre");
let filter = EnvFilter::builder() let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into()) .with_default_directive(LevelFilter::TRACE.into())
.parse_lossy("info,homecloud=trace"); .parse_lossy("info,yourcloud=trace");
tracing_subscriber::fmt().with_env_filter(filter).init(); tracing_subscriber::fmt().with_env_filter(filter).init();
} }

View file

@ -2,8 +2,9 @@
// pub use color_eyre; // pub use color_eyre;
pub use crate::config::Config; pub use crate::config::Config;
pub use anyhow::Error; pub use color_eyre::eyre::eyre;
pub use anyhow::Result; pub use color_eyre::Report;
pub use color_eyre::Result;
pub use redact::Secret; pub use redact::Secret;
pub use serde::{Deserialize, Serialize}; pub use serde::{Deserialize, Serialize};
pub use std::sync::Arc; pub use std::sync::Arc;

View file

@ -9,7 +9,7 @@ use reqwest::StatusCode;
use crate::{minecraft_server_status::MinecraftServerStatus, prelude::*}; use crate::{minecraft_server_status::MinecraftServerStatus, prelude::*};
struct WebserverError(anyhow::Error); struct WebserverError(Report);
type WebserverResult<T> = std::result::Result<T, WebserverError>; type WebserverResult<T> = std::result::Result<T, WebserverError>;
pub async fn start(_conf: Arc<Config>) -> Result<()> { pub async fn start(_conf: Arc<Config>) -> Result<()> {
@ -74,7 +74,7 @@ impl IntoResponse for WebserverError {
// `Result<_, AppError>`. That way you don't need to do that manually. // `Result<_, AppError>`. That way you don't need to do that manually.
impl<E> From<E> for WebserverError impl<E> From<E> for WebserverError
where where
E: Into<anyhow::Error>, E: Into<Report>,
{ {
fn from(err: E) -> Self { fn from(err: E) -> Self {
Self(err.into()) Self(err.into())