Base for RCON and Discord ends

This commit is contained in:
Malte Tammena 2021-11-17 10:53:05 +01:00
parent 4c00dc6930
commit 06ac88f6ff
12 changed files with 1353 additions and 15 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target
/result
/.env

1140
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,11 +2,20 @@
name = "glados"
version = "0.1.0"
edition = "2021"
license = "MIT"
[package.metadata.nix]
build = true
toolchain = "nightly"
[dependencies]
dotenv = "0.15.0"
lazy_static = "1.4.0"
rcon = "0.5.1"
serenity = "0.10.9"
structopt = "0.3.25"
thiserror = "1.0.30"
[dependencies.tokio]
version = "1.14.0"
features = [ "macros", "rt" ]

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# GLaDOS
Helper tool for my minecraft instance.
License: MIT

3
README.tpl Normal file
View file

@ -0,0 +1,3 @@
{{readme}}
License: {{license}}

View file

@ -2,11 +2,11 @@
"nodes": {
"devshell": {
"locked": {
"lastModified": 1636119665,
"narHash": "sha256-e11Z9PyH9hdgTm4Vyl8S5iTwrv0um6+srzb1Ba+YUHA=",
"lastModified": 1637098489,
"narHash": "sha256-IWBYLSNSENI/fTrXdYDhuCavxcgN9+RERrPM81f6DXY=",
"owner": "numtide",
"repo": "devshell",
"rev": "ab14b1a3cb253f58e02f5f849d621292fbf81fad",
"rev": "e8c2d4967b5c498b12551d1bb49352dcf9efa3e4",
"type": "github"
},
"original": {
@ -22,11 +22,11 @@
"rustOverlay": "rustOverlay"
},
"locked": {
"lastModified": 1637042995,
"narHash": "sha256-qogt7Qt/z859NrMRQHxPfaQj1BdK0+xNYa06ZU48dOo=",
"lastModified": 1637129418,
"narHash": "sha256-bO6rLgIiqK6pdeF2ewKyD6c+hNAcBEfXDqiTRaWzNmo=",
"owner": "yusdacra",
"repo": "nix-cargo-integration",
"rev": "298ea9dc79c5420a49976da3d20ca05d99b429f3",
"rev": "1faede2be6c28a68a00b3479b77c849720324511",
"type": "github"
},
"original": {
@ -37,11 +37,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1636800699,
"narHash": "sha256-SwbyVxXffu3G2ulJIbTf0iQfqhbGbdml4Dyv5j9BiAI=",
"lastModified": 1636976544,
"narHash": "sha256-9ZmdyoRz4Qu8bP5BKR1T10YbzcB9nvCeQjOEw2cRKR0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fa862644fc15ecb525eb8cd0a60276f1c340c7c",
"rev": "931ab058daa7e4cd539533963f95e2bb0dbd41e6",
"type": "github"
},
"original": {
@ -59,11 +59,11 @@
"rustOverlay": {
"flake": false,
"locked": {
"lastModified": 1637028901,
"narHash": "sha256-MJWtQbSZC1kNzJb2pg6mBcHTmIQdNBeQMznwbAfoqUg=",
"lastModified": 1637115307,
"narHash": "sha256-G+RKZeE1yLrnq+ExHF+HnSJsT+QJWebGhssgZHz3B00=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "ac1c572e4060c943e4077987da4b07bb7c3a491c",
"rev": "8a2e5fa870df3d34667d28fb3383d19516d182e4",
"type": "github"
},
"original": {

50
src/conf.rs Normal file
View file

@ -0,0 +1,50 @@
use std::collections::HashSet;
use lazy_static::lazy_static;
use serenity::model::id::UserId;
use structopt::StructOpt;
lazy_static! {
pub static ref ARGS: Args = Args::from_args();
}
#[derive(Debug, StructOpt)]
pub struct Args {
#[structopt(long,
short = "t",
value_name = "TOKEN",
validator = validate_token,
env = "DISCORD_TOKEN")]
pub discord_token: String,
#[structopt(long, value_name = "URL", env = "RCON_ADDRESS")]
pub rcon_address: String,
#[structopt(long, value_name = "PASSWORD", env = "RCON_PASSWORD")]
pub rcon_password: String,
#[structopt(long, short, value_name = "ID", env = "DISCORD_OWNERS")]
pub owners: Vec<UserId>,
#[structopt(
long,
short,
value_name = "PREFIX",
env = "DISCORD_PREFIX",
default_value = "~"
)]
pub prefix: String,
}
fn validate_token(raw: String) -> std::result::Result<(), String> {
serenity::client::validate_token(raw).map_err(|_| "Invalid discord token. Maybe a typo?".into())
}
impl Args {
pub fn owners(&self) -> HashSet<UserId> {
if self.owners.is_empty() {
eprintln!("You should probably specify at least one `--owner`");
}
self.owners.iter().cloned().collect()
}
}

40
src/discord.rs Normal file
View file

@ -0,0 +1,40 @@
use serenity::{
framework::{standard::macros::group, StandardFramework},
Client,
};
use crate::{
conf::ARGS,
error::{Error, Result},
};
pub struct Api {
client: Client,
}
#[group]
#[owners_only]
struct Owner;
impl Api {
pub async fn init() -> Result<Self> {
let framework = StandardFramework::new()
.configure(|c| {
c.with_whitespace(true)
.owners(ARGS.owners())
.prefix(&ARGS.prefix)
.delimiters(vec![", ", ","])
})
.group(&OWNER_GROUP);
let client = Client::builder(&ARGS.discord_token)
.framework(framework)
.await
.map_err(Error::CreatingDiscordClient)?;
Ok(Self { client })
}
pub async fn start(mut self) -> Result {
self.client.start().await?;
Ok(())
}
}

17
src/error.rs Normal file
View file

@ -0,0 +1,17 @@
use thiserror::Error;
pub type Result<T = ()> = ::std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Configuration key {_0:?} is missing. Try setting it through args or the environment")]
MissingConfiguration(String),
#[error("Failed to initialize RCON: {_0}")]
InitializingRcon(#[source] rcon::Error),
#[error("Failed to execute RCON command {_0:?}: {_1}")]
RconCommand(String, #[source] rcon::Error),
#[error("Failed to create the Discord client: {_0}")]
CreatingDiscordClient(#[source] serenity::Error),
#[error("Error while listening for Discord events: {_0}")]
Serenity(#[from] serenity::Error),
}

View file

@ -1,3 +1,27 @@
fn main() {
println!("Hello, world!");
//! # Gim's Library and Discord Orchestration System
//!
//! Helper tool for my minecraft instance.
mod conf;
mod discord;
mod error;
mod rcon;
mod state;
use error::Result;
use state::State;
#[tokio::main(flavor = "current_thread")]
async fn main() {
match real_main().await {
Ok(()) => {}
Err(why) => eprintln!("Error: {}", why),
}
}
async fn real_main() -> Result {
dotenv::dotenv().ok();
let state = State::init().await?;
state.run_bot().await
}

33
src/rcon.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::{
conf::ARGS,
error::{Error, Result},
};
pub struct Rcon {
inner: ::rcon::Connection,
}
impl Rcon {
pub async fn init() -> Result<Self> {
let addr = &ARGS.rcon_address;
let password = &ARGS.rcon_password;
let inner = rcon::Connection::builder()
.enable_minecraft_quirks(true)
.connect(addr, &password)
.await
.map_err(Error::InitializingRcon)?;
Ok(Self { inner })
}
pub async fn cmd(&mut self, cmd: &str) -> Result<String> {
self.inner
.cmd(cmd)
.await
.map_err(|why| Error::RconCommand(cmd.into(), why))
}
pub async fn greet(&mut self) -> Result {
let _ = self.cmd("say GLaDOS is now online").await?;
Ok(())
}
}

18
src/state.rs Normal file
View file

@ -0,0 +1,18 @@
use crate::{discord::Api, error::Result, rcon::Rcon};
pub struct State {
pub rcon: Rcon,
pub api: Api,
}
impl State {
pub async fn init() -> Result<Self> {
let rcon = Rcon::init().await?;
let api = Api::init().await?;
Ok(State { rcon, api })
}
pub async fn run_bot(self) -> Result {
self.api.start().await
}
}