Basics are in place

This commit is contained in:
Daniel Flanagan 2024-04-11 17:03:25 -05:00
parent 4226f7c143
commit c935d725aa
9 changed files with 625 additions and 45 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
/.direnv /.direnv
/.pre-commit-config.yaml /.pre-commit-config.yaml
/conf.toml

337
Cargo.lock generated
View file

@ -233,6 +233,18 @@ name = "bitflags"
version = "2.5.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
dependencies = [
"serde",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@ -284,6 +296,7 @@ dependencies = [
"axum", "axum",
"clap", "clap",
"color-eyre", "color-eyre",
"config",
"discord", "discord",
"redact", "redact",
"reqwest", "reqwest",
@ -374,6 +387,55 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "config"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
dependencies = [
"async-trait",
"convert_case",
"json5",
"lazy_static",
"nom",
"pathdiff",
"ron",
"rust-ini",
"serde",
"serde_json",
"toml",
"yaml-rust",
]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.14",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -390,6 +452,15 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.0" version = "1.4.0"
@ -399,6 +470,22 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.8" version = "0.20.8"
@ -444,6 +531,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "discord" name = "discord"
version = "0.9.0" version = "0.9.0"
@ -466,6 +563,15 @@ dependencies = [
"websocket", "websocket",
] ]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "ed25519" name = "ed25519"
version = "1.5.3" version = "1.5.3"
@ -657,6 +763,16 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check 0.9.4",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@ -712,6 +828,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.14.3"
@ -974,6 +1096,17 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.2.2" version = "0.2.2"
@ -1004,6 +1137,12 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.13"
@ -1093,6 +1232,12 @@ dependencies = [
"unicase 2.7.0", "unicase 2.7.0",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.2" version = "0.7.2"
@ -1155,6 +1300,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -1269,6 +1424,16 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "ordered-multimap"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
dependencies = [
"dlv-list",
"hashbrown 0.13.2",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -1306,6 +1471,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "1.0.1" version = "1.0.1"
@ -1318,6 +1489,51 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.5" version = "1.1.5"
@ -1673,6 +1889,28 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
] ]
[[package]]
name = "ron"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64 0.21.7",
"bitflags 2.5.0",
"serde",
"serde_derive",
]
[[package]]
name = "rust-ini"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
dependencies = [
"cfg-if 1.0.0",
"ordered-multimap",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -1820,6 +2058,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -1862,6 +2109,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -2053,6 +2311,15 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2132,6 +2399,40 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
dependencies = [
"indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.4.13" version = "0.4.13"
@ -2250,6 +2551,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "1.4.2" version = "1.4.2"
@ -2289,6 +2602,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "url" name = "url"
version = "1.7.2" version = "1.7.2"
@ -2663,6 +2982,15 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winnow"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.52.0" version = "0.52.0"
@ -2672,3 +3000,12 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View file

@ -8,6 +8,7 @@ anyhow = "1.0.82"
axum = "0.7.5" axum = "0.7.5"
clap = "4.5.4" clap = "4.5.4"
color-eyre = "0.6.3" color-eyre = "0.6.3"
config = "0.14.0"
discord = { git = "https://github.com/SpaceManiac/discord-rs" } discord = { git = "https://github.com/SpaceManiac/discord-rs" }
redact = { version = "0.1.9", features = ["serde"] } redact = { version = "0.1.9", features = ["serde"] }
reqwest = { version = "0.12.3", features = ["json", "socks"] } reqwest = { version = "0.12.3", features = ["json", "socks"] }

185
src/chatbot.rs Normal file
View file

@ -0,0 +1,185 @@
// TODO: family reminders?
// TODO: connect to Discord
// TODO: handle messages
// TODO: data persistence? (sqlite? sled?)
use std::{any::Any, future};
use discord::{
model::{Event, Message},
Discord,
};
use crate::prelude::*;
pub async fn start(conf: Arc<Config>) -> Result<()> {
if conf.discord.is_none() {
warn!("Chatbot starting without Discord token. Nothing will happen.");
// wait forever
future::pending::<()>().await;
}
let discord = Discord::from_bot_token(
conf.discord
.as_ref()
.unwrap()
.bot_token
.clone()
.expose_secret(),
)?;
let (mut conn, ready_ev) = discord.connect()?;
info!("Discord connection ready: {ready_ev:?}");
while let Ok(event) = conn.recv_event() {
tokio::spawn(async move {
match handle_event(event).await {
Ok(_) => {}
Err(err) => {
error!("Failed to handle Discord event: {err}")
}
}
});
}
Ok(())
}
pub async fn handle_event(event: Event) -> Result<()> {
match event {
Event::Ready(_) => return Ok(ignore_event(event)),
Event::Resumed { trace: _ } => return Ok(ignore_event(event)),
Event::UserUpdate(_) => return Ok(ignore_event(event)),
Event::UserNoteUpdate(_, _) => return Ok(ignore_event(event)),
Event::UserSettingsUpdate {
detect_platform_accounts: _,
developer_mode: _,
enable_tts_command: _,
inline_attachment_media: _,
inline_embed_media: _,
locale: _,
message_display_compact: _,
render_embeds: _,
server_positions: _,
show_current_game: _,
status: _,
theme: _,
convert_emoticons: _,
friend_source_flags: _,
} => return Ok(ignore_event(event)),
Event::UserServerSettingsUpdate(_) => return Ok(ignore_event(event)),
Event::VoiceStateUpdate(_, _) => return Ok(ignore_event(event)),
Event::VoiceServerUpdate {
server_id: _,
channel_id: _,
endpoint: _,
token: _,
} => return Ok(ignore_event(event)),
Event::CallCreate(_) => return Ok(ignore_event(event)),
Event::CallUpdate {
channel_id: _,
message_id: _,
region: _,
ringing: _,
} => return Ok(ignore_event(event)),
Event::CallDelete(_) => return Ok(ignore_event(event)),
Event::ChannelRecipientAdd(_, _) => return Ok(ignore_event(event)),
Event::ChannelRecipientRemove(_, _) => return Ok(ignore_event(event)),
Event::TypingStart {
channel_id: _,
user_id: _,
timestamp: _,
} => return Ok(ignore_event(event)),
Event::PresenceUpdate {
presence: _,
server_id: _,
roles: _,
} => return Ok(ignore_event(event)),
Event::PresencesReplace(_) => return Ok(ignore_event(event)),
Event::RelationshipAdd(_) => return Ok(ignore_event(event)),
Event::RelationshipRemove(_, _) => return Ok(ignore_event(event)),
Event::MessageCreate(msg) => return Ok(handle_message(msg)),
Event::MessageUpdate {
id: _,
channel_id: _,
kind: _,
content: _,
nonce: _,
tts: _,
pinned: _,
timestamp: _,
edited_timestamp: _,
author: _,
mention_everyone: _,
mentions: _,
mention_roles: _,
attachments: _,
embeds: _,
} => return Ok(ignore_event(event)),
Event::MessageAck {
channel_id: _,
message_id: _,
} => return Ok(ignore_event(event)),
Event::MessageDelete {
channel_id: _,
message_id: _,
} => return Ok(ignore_event(event)),
Event::MessageDeleteBulk {
channel_id: _,
ids: _,
} => return Ok(ignore_event(event)),
Event::ServerCreate(_) => return Ok(ignore_event(event)),
Event::ServerUpdate(_) => return Ok(ignore_event(event)),
Event::ServerDelete(_) => return Ok(ignore_event(event)),
Event::ServerMemberAdd(_, _) => return Ok(ignore_event(event)),
Event::ServerMemberUpdate {
server_id: _,
roles: _,
user: _,
nick: _,
} => return Ok(ignore_event(event)),
Event::ServerMemberRemove(_, _) => return Ok(ignore_event(event)),
Event::ServerMembersChunk(_, _) => return Ok(ignore_event(event)),
Event::ServerSync {
server_id: _,
large: _,
members: _,
presences: _,
} => return Ok(ignore_event(event)),
Event::ServerRoleCreate(_, _) => return Ok(ignore_event(event)),
Event::ServerRoleUpdate(_, _) => return Ok(ignore_event(event)),
Event::ServerRoleDelete(_, _) => return Ok(ignore_event(event)),
Event::ServerBanAdd(_, _) => return Ok(ignore_event(event)),
Event::ServerBanRemove(_, _) => return Ok(ignore_event(event)),
Event::ServerIntegrationsUpdate(_) => return Ok(ignore_event(event)),
Event::ServerEmojisUpdate(_, _) => return Ok(ignore_event(event)),
Event::ChannelCreate(_) => return Ok(ignore_event(event)),
Event::ChannelUpdate(_) => return Ok(ignore_event(event)),
Event::ChannelDelete(_) => return Ok(ignore_event(event)),
Event::ChannelPinsAck {
channel_id: _,
timestamp: _,
} => return Ok(ignore_event(event)),
Event::ChannelPinsUpdate {
channel_id: _,
last_pin_timestamp: _,
} => return Ok(ignore_event(event)),
Event::ReactionAdd(_) => return Ok(ignore_event(event)),
Event::ReactionRemove(_) => return Ok(ignore_event(event)),
Event::Unknown(_, _) => return Ok(ignore_event(event)),
_ => return Ok(ignore_event(event)),
}
}
pub fn ignore_event(event: Event) -> () {
let event_type_id = event.type_id();
info!("Ignoring Discord event of type: {event_type_id:?}")
}
pub fn handle_message(msg: Message) -> () {
info!(
"Recieved Discord message in channel {} with content: {}",
msg.channel_id, msg.content
);
return ();
}

View file

@ -1,48 +1,76 @@
use std::fmt::Debug; use std::{collections::HashMap, fmt::Debug};
use config::{builder::DefaultState, Config as CConfig, ConfigBuilder};
use redact::serde::redact_secret;
use crate::prelude::*; use crate::prelude::*;
const CURRENT_VERSION: u64 = 1;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Discord {
#[serde(serialize_with = "redact_secret")]
pub bot_token: Secret<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OpenAI {
#[serde(serialize_with = "redact_secret")]
pub token: Secret<String>,
}
// pub static ref CONFIG: Config = Config::load_or_defaults(); // pub static ref CONFIG: Config = Config::load_or_defaults();
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Config { pub struct Config {
pub version: u64, pub version: u64,
pub secret: String, pub discord: Option<Discord>,
pub open_ai: Option<OpenAI>,
#[serde(serialize_with = "redact_secret")]
pub secret: Secret<String>,
} }
impl Config { impl Config {
/// Loads a configuration via the predetermined methods, overlaying a pub fn load() -> Result<Self> {
/// default configuration. Returns any relevant errors that occurred let c = Self::builder()?.build()?;
/// during loading to be reported later to the user after logging (or other Ok(c.try_deserialize()?)
/// reporting faculties) have been initialized.
pub fn load_or_defaults() -> (Self, Option<Error>) {
let config = Self::defaults();
(config, None)
} }
pub fn defaults() -> Self { pub fn builder() -> Result<ConfigBuilder<DefaultState>> {
Self { // TODO: log whether or not we were able to load conf.toml?
version: 1, Ok(Self::default_builder()
secret: "this is a secret!".to_owned(), .map_err(Error::from)?
} .add_source(config::File::from(std::path::PathBuf::from("./conf.toml")).required(false))
} .add_source(config::Environment::with_prefix("chatbot").separator("__")))
} }
impl Debug for Config { pub fn default_builder() -> Result<ConfigBuilder<DefaultState>> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(CConfig::builder()
f.debug_struct("Config") .set_default("version", CURRENT_VERSION)?
.field("version", &self.version) .set_default("discord", Option::<HashMap<String, config::Value>>::None)?
.finish() .set_default("open_ai", Option::<HashMap<String, config::Value>>::None)?
.set_default("secret", "this is a secret!")?)
}
pub fn defaults() -> Result<Self> {
let c = Self::default_builder()?.build()?;
Ok(c.try_deserialize()?)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Config; use crate::prelude::*;
#[test] #[test]
fn config_does_not_leak_secrets() { fn config_does_not_leak_secrets() -> Result<()> {
let defaults = Config::defaults(); let defaults = Config::default_builder()?
.set_override("discord.bot_token", Secret::new("THIS_IS_MY_BOT_TOKEN"))?
.build()?;
let s = format!("{defaults:?}"); let s = format!("{defaults:?}");
assert!(!s.contains("secret")); println!("{s}");
assert!(!s.contains("THIS_IS_MY_BOT_TOKEN"));
assert!(!s.contains("this is a secret")); assert!(!s.contains("this is a secret"));
Ok(())
} }
} }

View file

@ -1,5 +1,6 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
mod chatbot;
mod config; mod config;
mod minecraft_server_status; mod minecraft_server_status;
mod observe; mod observe;
@ -10,22 +11,34 @@ mod webserver;
use crate::prelude::*; use crate::prelude::*;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> Result<()> {
let (conf, conf_err) = Config::load_or_defaults(); let conf: Arc<Config> = match Config::load() {
observe::setup_logging(&conf); Err(err) => {
error!("Error loading configuration: {err}");
return Err(err);
}
Ok(conf) => Arc::new(conf),
};
observe::setup_logging(conf.clone());
debug!("Configuration: {conf:?}"); debug!("Configuration: {conf:?}");
if let Some(err) = conf_err {
warn!("Error loading configuration: {err}"); let mut set = tokio::task::JoinSet::new();
set.spawn(webserver::start(conf.clone()));
set.spawn(chatbot::start(conf.clone()));
let result = set.join_next().await;
match result {
Some(Err(err)) => {
error!("One of the JoinSet tasks encountered a JoinError: {err}");
Err(err.into())
}
Some(Ok(Err(err))) => {
error!("One of the JoinSet tasks encountered an error: {err}");
Err(err)
}
_ => {
unreachable!("One of the primary spawned tasks finished. Exiting...");
}
} }
// TODO: oh yeah we need an HTTP server to handle minecraft server status stuff
let server = webserver::start(&conf);
// TODO: family reminders?
// TODO: connect to Discord
// TODO: handle messages
// TODO: data persistence? (sqlite? sled?)
server.await
} }

View file

@ -3,12 +3,12 @@ use tracing_subscriber::EnvFilter;
use crate::prelude::*; use crate::prelude::*;
pub fn setup_logging(_conf: &Config) { 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("trace,chatbot=trace"); .parse_lossy("info,chatbot=trace");
tracing_subscriber::fmt().with_env_filter(filter).init(); tracing_subscriber::fmt().with_env_filter(filter).init();
} }

View file

@ -6,4 +6,5 @@ pub use anyhow::Error;
pub use anyhow::Result; pub use anyhow::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 tracing::{debug, error, info, trace, warn}; pub use tracing::{debug, error, info, trace, warn};

View file

@ -10,24 +10,38 @@ use reqwest::StatusCode;
use crate::{minecraft_server_status::MinecraftServerStatus, prelude::*}; use crate::{minecraft_server_status::MinecraftServerStatus, prelude::*};
struct WebserverError(anyhow::Error); struct WebserverError(anyhow::Error);
type Result<T> = std::result::Result<T, WebserverError>; type WebserverResult<T> = std::result::Result<T, WebserverError>;
pub async fn start(_conf: &Config) { pub async fn start(_conf: Arc<Config>) -> Result<()> {
let app = Router::new() let app = Router::new()
.route("/", get(hello_world)) .route("/", get(hello_world))
.route("/health", get(health))
.route("/minecraft-server-status", get(minecraft_server_status)); .route("/minecraft-server-status", get(minecraft_server_status));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
info!("Listening on {:?}", listener); info!("Listening on {:?}", listener);
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
Ok(())
} }
async fn hello_world() -> impl IntoResponse { async fn hello_world() -> impl IntoResponse {
"Hello, World!".to_owned() "Hello, World!".to_owned()
} }
async fn minecraft_server_status() -> Result<impl IntoResponse> { #[derive(Serialize, Debug)]
struct Health {
status: String,
}
async fn health() -> impl IntoResponse {
Json(Health {
status: "online".to_owned(),
})
}
async fn minecraft_server_status() -> WebserverResult<impl IntoResponse> {
let c = Arc::new(MinecraftServerStatus::try_new()?); let c = Arc::new(MinecraftServerStatus::try_new()?);
let reqs: Vec<&str> = vec!["h.lyte.dev:26965", "ourcraft.lyte.dev"]; let reqs: Vec<&str> = vec!["h.lyte.dev:26965", "ourcraft.lyte.dev"];
let mut futures = Vec::with_capacity(reqs.len()); let mut futures = Vec::with_capacity(reqs.len());