Compare commits
4 Commits
6b4eb7c271
...
d2c22bcb40
Author | SHA1 | Date | |
---|---|---|---|
d2c22bcb40 | |||
a7bee7331b | |||
42952ce9cd | |||
d2cc211209 |
@ -1,5 +1,5 @@
|
|||||||
[registry]
|
[registry]
|
||||||
default = "crates-io"
|
default = "crates-io"
|
||||||
|
|
||||||
[registries.vcs-elnafo]
|
[registries.elnafo-vcs]
|
||||||
index = "sparse+https://vcs.elnafo.ru/api/packages/L-Nafaryus/cargo/"
|
index = "sparse+https://vcs.elnafo.ru/api/packages/L-Nafaryus/cargo/"
|
||||||
|
@ -6,6 +6,12 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: nix-runner
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: |
|
||||||
|
NIXPKGS_ALLOW_BROKEN=1 nix flake check --allow-import-from-derivation --keep-going --impure
|
||||||
build:
|
build:
|
||||||
runs-on: nix-runner
|
runs-on: nix-runner
|
||||||
steps:
|
steps:
|
||||||
|
1096
Cargo.lock
generated
1096
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@ -1,18 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oscuro"
|
name = "oscuro"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Oscuro - a fancy discord bot"
|
description = "Oscuro is a fancy multibot"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://vcs.elnafo.ru/L-Nafaryus/oscuro"
|
repository = "https://vcs.elnafo.ru/L-Nafaryus/oscuro"
|
||||||
publish = ["vcs-elnafo"]
|
publish = ["vcs-elnafo"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
oscuro-core = { version = "0.1.0", path = "crates/oscuro-core", registry = "vcs-elnafo" }
|
async-process = "2.2.3"
|
||||||
|
poise = "0.6.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
|
serde_json = "1.0.120"
|
||||||
|
teloxide = { version = "0.12.2", features = ["macros"] }
|
||||||
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
toml = "0.8.15"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["crates/oscuro-core", "crates/oscuro-shuttle", "crates/oscuro-telegram"]
|
members = []
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "oscuro-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "Core of a fancy discord bot Oscuro"
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://vcs.elnafo.ru/L-Nafaryus/oscuro"
|
|
||||||
publish = ["vcs-elnafo"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
poise = "0.6.1"
|
|
||||||
rand = "0.8.5"
|
|
||||||
serde = { version = "1.0.198", features = ["derive"] }
|
|
||||||
serde_json = "1.0.116"
|
|
||||||
teloxide = { version = "0.12.2", features = ["macros"] }
|
|
||||||
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
|
||||||
toml = "0.8.12"
|
|
||||||
tracing = "0.1.37"
|
|
@ -1,40 +0,0 @@
|
|||||||
use poise::serenity_prelude as serenity;
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
use super::errors::BoxedError;
|
|
||||||
use super::Context;
|
|
||||||
|
|
||||||
#[poise::command(prefix_command)]
|
|
||||||
pub async fn register(ctx: Context<'_>) -> Result<(), BoxedError> {
|
|
||||||
poise::builtins::register_application_commands_buttons(ctx).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
|
||||||
pub async fn age(
|
|
||||||
ctx: Context<'_>,
|
|
||||||
#[description = "Ooph user"] user: Option<serenity::User>,
|
|
||||||
) -> Result<(), BoxedError> {
|
|
||||||
let u = user.as_ref().unwrap_or_else(|| ctx.author());
|
|
||||||
let response = format!("{}'s account was created at {}", u.name, u.created_at());
|
|
||||||
ctx.say(response).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
|
||||||
pub async fn dice(ctx: Context<'_>) -> Result<(), BoxedError> {
|
|
||||||
let number = {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
rng.gen_range(1..21)
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = format!("{} throws {}.", ctx.author(), number);
|
|
||||||
let response = match number {
|
|
||||||
20 => format!("{} Critical success.", response),
|
|
||||||
1 => format!("{} Critical failure.", response),
|
|
||||||
_ => response,
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.say(response).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
pub mod commands;
|
|
||||||
pub mod config;
|
|
||||||
pub mod errors;
|
|
||||||
|
|
||||||
use errors::BoxedError;
|
|
||||||
use poise::serenity_prelude::{self as serenity, prelude::TypeMapKey, Client};
|
|
||||||
|
|
||||||
use teloxide::prelude::*;
|
|
||||||
use teloxide::types::Recipient;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AppState {
|
|
||||||
pub config: config::Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeMapKey for AppState {
|
|
||||||
type Value = AppState;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Context<'a> = poise::Context<'a, AppState, BoxedError>;
|
|
||||||
|
|
||||||
pub async fn client(state: AppState) -> Result<Client, BoxedError> {
|
|
||||||
let intents = serenity::GatewayIntents::non_privileged();
|
|
||||||
let state_copy = state.clone();
|
|
||||||
let framework = poise::Framework::builder()
|
|
||||||
.options(poise::FrameworkOptions {
|
|
||||||
commands: vec![commands::register(), commands::age(), commands::dice()],
|
|
||||||
event_handler: |ctx, event, framework, data| {
|
|
||||||
Box::pin(event_handler(ctx, event, framework, data))
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.setup(|ctx, _ready, framework| {
|
|
||||||
Box::pin(async move {
|
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
|
||||||
Ok(state_copy)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let client = serenity::ClientBuilder::new(state.clone().config.discord_token, intents)
|
|
||||||
.framework(framework)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut data = client.data.write().await;
|
|
||||||
data.insert::<AppState>(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn event_handler(
|
|
||||||
_ctx: &serenity::Context,
|
|
||||||
event: &serenity::FullEvent,
|
|
||||||
_framework: poise::FrameworkContext<'_, AppState, BoxedError>,
|
|
||||||
_state: &AppState,
|
|
||||||
) -> Result<(), BoxedError> {
|
|
||||||
match event {
|
|
||||||
serenity::FullEvent::Ready { data_about_bot, .. } => {
|
|
||||||
println!("Logged in as {}", data_about_bot.user.name);
|
|
||||||
}
|
|
||||||
serenity::FullEvent::Message { new_message } => {
|
|
||||||
println!("{:?}", new_message.clone());
|
|
||||||
let bot = Bot::from_env();
|
|
||||||
bot.send_message(
|
|
||||||
Recipient::Id(ChatId(-4221527632)),
|
|
||||||
new_message.author.name.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("err");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "oscuro-shuttle"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[package.metadata.release]
|
|
||||||
release = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
oscuro-core = { version = "0.1.0", path = "../oscuro-core" }
|
|
||||||
shuttle-runtime = "0.43.0"
|
|
||||||
shuttle-serenity = "0.43.0"
|
|
@ -1,20 +0,0 @@
|
|||||||
use oscuro_core::{client, config::Config, AppState};
|
|
||||||
|
|
||||||
#[shuttle_runtime::main]
|
|
||||||
async fn main(
|
|
||||||
#[shuttle_runtime::Secrets] secrets: shuttle_runtime::SecretStore,
|
|
||||||
) -> shuttle_serenity::ShuttleSerenity {
|
|
||||||
let token = secrets
|
|
||||||
.get("discord_token")
|
|
||||||
.expect("Variable 'DISCORD_TOKEN' must be set");
|
|
||||||
|
|
||||||
let state = AppState {
|
|
||||||
config: Config {
|
|
||||||
discord_token: token,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = client(state).await.expect("Failed to create client");
|
|
||||||
|
|
||||||
Ok(client.into())
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "oscuro-telegram"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = ["vcs-elnafo"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
log = "0.4.22"
|
|
||||||
pretty_env_logger = "0.5.0"
|
|
||||||
serenity = "0.12.2"
|
|
||||||
teloxide = { version = "0.12.2", features = ["macros"] }
|
|
||||||
tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros"] }
|
|
@ -1,31 +0,0 @@
|
|||||||
use serenity::builder::ExecuteWebhook;
|
|
||||||
use serenity::http::Http;
|
|
||||||
use serenity::model::webhook::Webhook;
|
|
||||||
use teloxide::prelude::*;
|
|
||||||
use teloxide::types::Recipient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
pretty_env_logger::init();
|
|
||||||
log::info!("Starting throw dice bot...");
|
|
||||||
|
|
||||||
let bot = Bot::from_env();
|
|
||||||
|
|
||||||
/*let http = Http::new("");
|
|
||||||
let webhook = Webhook::from_url(&http, "https://discord.com/api/webhooks/1259860143579987999/whI0ozB5uc17Wdzkb2-HSrVGi8h_MyR2_4eyCsGuGpQN4KcjMhq7rfQH1JIdbD1HNaW_")
|
|
||||||
.await
|
|
||||||
.expect("Replace the webhook with your own");
|
|
||||||
|
|
||||||
let builder = ExecuteWebhook::new().content("hello there").username("Webhook test");
|
|
||||||
webhook.execute(&http, false, builder).await.expect("Could not execute webhook.");
|
|
||||||
*/
|
|
||||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
|
||||||
bot.send_dice(msg.chat.id).await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
/*bot.send_message(Recipient::Id(ChatId(-4221527632)), "Heya!")
|
|
||||||
.await
|
|
||||||
.expect("err");*/
|
|
||||||
}
|
|
907
flake.lock
907
flake.lock
File diff suppressed because it is too large
Load Diff
81
flake.nix
81
flake.nix
@ -1,14 +1,23 @@
|
|||||||
{
|
{
|
||||||
description = "Oscuro - a fancy discord bot";
|
description = "Oscuro is a fancy multibot";
|
||||||
|
|
||||||
nixConfig = {
|
nixConfig = {
|
||||||
extra-substituters = ["https://bonfire.cachix.org"];
|
extra-substituters = [
|
||||||
extra-trusted-public-keys = ["bonfire.cachix.org-1:mzAGBy/Crdf8NhKail5ciK7ZrGRbPJJobW6TwFb7WYM="];
|
"https://cache.elnafo.ru"
|
||||||
|
"https://bonfire.cachix.org"
|
||||||
|
];
|
||||||
|
extra-trusted-public-keys = [
|
||||||
|
"cache.elnafo.ru:j3VD+Hn+is2Qk3lPXDSdPwHJQSatizk7V82iJ2RP1yo="
|
||||||
|
"bonfire.cachix.org-1:mzAGBy/Crdf8NhKail5ciK7ZrGRbPJJobW6TwFb7WYM="
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
bonfire = {
|
bonfire = {
|
||||||
url = "github:L-Nafaryus/bonfire";
|
url = "github:L-Nafaryus/bonfire";
|
||||||
|
inputs = {
|
||||||
|
oscuro.follows = "";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
nixpkgs.follows = "bonfire/nixpkgs";
|
nixpkgs.follows = "bonfire/nixpkgs";
|
||||||
};
|
};
|
||||||
@ -19,49 +28,57 @@
|
|||||||
bonfire,
|
bonfire,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
forAllSystems = nixpkgs.lib.genAttrs ["x86_64-linux"];
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||||
nixpkgsFor = forAllSystems (system: import nixpkgs {inherit system;});
|
lib = pkgs.lib;
|
||||||
|
fenixPkgs = bonfire.inputs.fenix.packages.x86_64-linux;
|
||||||
|
craneLib = (bonfire.inputs.crane.mkLib pkgs).overrideToolchain fenixPkgs.complete.toolchain;
|
||||||
in {
|
in {
|
||||||
packages = forAllSystems (system: let
|
packages.x86_64-linux = rec {
|
||||||
pkgs = nixpkgsFor.${system};
|
oscuro = let
|
||||||
crane-lib = bonfire.inputs.crane.lib.${system};
|
common = {
|
||||||
|
|
||||||
src = pkgs.lib.cleanSourceWith {
|
src = pkgs.lib.cleanSourceWith {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
filter = path: type: (crane-lib.filterCargoSources path type);
|
filter = path: type: (craneLib.filterCargoSources path type);
|
||||||
};
|
};
|
||||||
|
|
||||||
common = {
|
strictDeps = true;
|
||||||
inherit src;
|
|
||||||
|
nativeBuildInputs = [pkgs.pkg-config pkgs.makeWrapper];
|
||||||
|
|
||||||
|
buildInputs = [pkgs.openssl];
|
||||||
|
};
|
||||||
|
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly common;
|
||||||
|
in
|
||||||
|
craneLib.buildPackage (common
|
||||||
|
// rec {
|
||||||
pname = "oscuro";
|
pname = "oscuro";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
strictDeps = true;
|
inherit cargoArtifacts;
|
||||||
};
|
postInstall = ''
|
||||||
|
wrapProgram $out/bin/${pname} \
|
||||||
cargoArtifacts = crane-lib.buildDepsOnly common;
|
--prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath common.buildInputs} \
|
||||||
in {
|
'';
|
||||||
oscuro = crane-lib.buildPackage (common // {inherit cargoArtifacts;});
|
|
||||||
|
|
||||||
default = self.packages.${system}.oscuro;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = forAllSystems (system: let
|
default = oscuro;
|
||||||
pkgs = nixpkgsFor.${system};
|
};
|
||||||
bonfire-pkgs = bonfire.packages.${system};
|
|
||||||
fenix-pkgs = bonfire.inputs.fenix.packages.${system};
|
hydraJobs = {
|
||||||
in {
|
packages = self.packages;
|
||||||
default = pkgs.mkShell {
|
};
|
||||||
|
|
||||||
|
devShells.x86_64-linux.default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = [pkgs.pkg-config];
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
fenix-pkgs.complete.toolchain
|
fenixPkgs.complete.toolchain
|
||||||
bonfire-pkgs.cargo-shuttle
|
|
||||||
pkgs.cargo-release
|
pkgs.cargo-release
|
||||||
pkgs.pkg-config
|
|
||||||
pkgs.openssl
|
pkgs.openssl
|
||||||
];
|
];
|
||||||
|
LD_LIBRARY_PATH = lib.makeLibraryPath [pkgs.openssl];
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
nixosModules = {
|
nixosModules = rec {
|
||||||
oscuro = {
|
oscuro = {
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
@ -148,7 +165,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
default = self.nixosModules.oscuro;
|
default = oscuro;
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosConfigurations.oscuro = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.oscuro = nixpkgs.lib.nixosSystem {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub discord_token: String,
|
pub discord_token: Option<String>,
|
||||||
|
pub telegram_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -28,12 +30,24 @@ impl Config {
|
|||||||
pub fn write(&self, path: &std::path::Path) -> Result<(), ConfigError> {
|
pub fn write(&self, path: &std::path::Path) -> Result<(), ConfigError> {
|
||||||
Ok(fs::write(path, self.to_string()?)?)
|
Ok(fs::write(path, self.to_string()?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_env(mut self) -> Self {
|
||||||
|
if let Ok(token) = env::var("OSCURO_DISCORD_TOKEN") {
|
||||||
|
self.discord_token = Some(token);
|
||||||
|
};
|
||||||
|
if let Ok(token) = env::var("OSCURO_TELEGRAM_TOKEN") {
|
||||||
|
self.telegram_token = Some(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
discord_token: String::new(),
|
discord_token: Some(String::new()),
|
||||||
|
telegram_token: Some(String::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,10 +72,10 @@ impl std::error::Error for ConfigError {}
|
|||||||
impl std::fmt::Display for ConfigError {
|
impl std::fmt::Display for ConfigError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Parse => write!(f, "Failed to parse Config from string"),
|
Self::Parse => write!(f, "Failed to parse config from string"),
|
||||||
Self::StringParse => write!(f, "Failed to parse environment variable"),
|
Self::StringParse => write!(f, "Failed to parse environment variable"),
|
||||||
Self::Serialize => write!(f, "Failed to serialize Config to TOML"),
|
Self::Serialize => write!(f, "Failed to serialize Config to TOML"),
|
||||||
Self::IO => write!(f, "Faild to write file"),
|
Self::IO => write!(f, "Failed to write file"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
84
src/discord/commands.rs
Normal file
84
src/discord/commands.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use async_process::Command;
|
||||||
|
use poise::serenity_prelude as serenity;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::errors::BoxedError;
|
||||||
|
|
||||||
|
#[poise::command(prefix_command)]
|
||||||
|
pub async fn register(ctx: Context<'_>) -> Result<(), BoxedError> {
|
||||||
|
poise::builtins::register_application_commands_buttons(ctx).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command)]
|
||||||
|
pub async fn age(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Ooph user"] user: Option<serenity::User>,
|
||||||
|
) -> Result<(), BoxedError> {
|
||||||
|
let u = user.as_ref().unwrap_or_else(|| ctx.author());
|
||||||
|
let response = format!("{}'s account was created at {}", u.name, u.created_at());
|
||||||
|
ctx.say(response).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command)]
|
||||||
|
pub async fn dice(ctx: Context<'_>) -> Result<(), BoxedError> {
|
||||||
|
let number = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
rng.gen_range(1..21)
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = format!("{} throws {}.", ctx.author(), number);
|
||||||
|
let response = match number {
|
||||||
|
20 => format!("{} Critical success.", response),
|
||||||
|
1 => format!("{} Critical failure.", response),
|
||||||
|
_ => response,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.say(response).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, poise::ChoiceParameter)]
|
||||||
|
pub enum ServiceChoice {
|
||||||
|
#[name = "Elnafo VCS"]
|
||||||
|
ElnafoVcs,
|
||||||
|
#[name = "Elnafo Mail"]
|
||||||
|
ElnafoMail,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command)]
|
||||||
|
pub async fn status(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Check service status"] service: ServiceChoice,
|
||||||
|
) -> Result<(), BoxedError> {
|
||||||
|
let mut systemctl = Command::new("systemctl");
|
||||||
|
let service_info = match service {
|
||||||
|
ServiceChoice::ElnafoVcs => systemctl.arg("show").arg("gitea.service"),
|
||||||
|
ServiceChoice::ElnafoMail => systemctl.arg("show").arg("acpid.service"),
|
||||||
|
};
|
||||||
|
let output = service_info.output().await?;
|
||||||
|
|
||||||
|
let mut data: HashMap<&str, &str> = HashMap::new();
|
||||||
|
|
||||||
|
for line in str::from_utf8(&output.stdout)?.lines() {
|
||||||
|
let kv: Vec<&str> = line.split('=').collect();
|
||||||
|
data.insert(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
println!("{:?} {:?}", data["LoadState"], data["SubState"]);
|
||||||
|
|
||||||
|
if data["LoadState"] == "loaded" && data["SubState"] == "running" {
|
||||||
|
ctx.say(format!(
|
||||||
|
"{:?} is up and running for {}",
|
||||||
|
service, data["ExecMainStartTimestamp"]
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
ctx.say(format!("{:?} is dead", service)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
148
src/discord/mod.rs
Normal file
148
src/discord/mod.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
pub mod commands;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::errors::BoxedError;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use poise::serenity_prelude::{
|
||||||
|
self as serenity,
|
||||||
|
builder::{CreateEmbed, CreateMessage},
|
||||||
|
model::id::ChannelId,
|
||||||
|
prelude::TypeMapKey,
|
||||||
|
//Client,
|
||||||
|
};
|
||||||
|
use serenity::GatewayIntents;
|
||||||
|
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
use teloxide::types::Recipient;
|
||||||
|
|
||||||
|
use crate::telegram;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BotState {
|
||||||
|
pub config: Config,
|
||||||
|
pub telegram_agent: Option<telegram::Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeMapKey for BotState {
|
||||||
|
type Value = BotState;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context<'a> = poise::Context<'a, BotState, BoxedError>;
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
client: serenity::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub async fn new(config: Config) -> Result<Self, BoxedError> {
|
||||||
|
let telegram_agent = if config.clone().telegram_token.is_some() {
|
||||||
|
Some(telegram::Client::new(config.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = BotState {
|
||||||
|
config: config.clone(),
|
||||||
|
telegram_agent: telegram_agent,
|
||||||
|
};
|
||||||
|
|
||||||
|
let intents = GatewayIntents::GUILDS
|
||||||
|
| GatewayIntents::GUILD_MESSAGES
|
||||||
|
| GatewayIntents::MESSAGE_CONTENT;
|
||||||
|
let state_copy = state.clone();
|
||||||
|
let framework = poise::Framework::builder()
|
||||||
|
.options(poise::FrameworkOptions {
|
||||||
|
commands: vec![
|
||||||
|
commands::register(),
|
||||||
|
commands::age(),
|
||||||
|
commands::dice(),
|
||||||
|
commands::status(),
|
||||||
|
],
|
||||||
|
event_handler: |ctx, event, framework, data| {
|
||||||
|
Box::pin(event_handler(ctx, event, framework, data))
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.setup(|ctx, _ready, framework| {
|
||||||
|
Box::pin(async move {
|
||||||
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
|
Ok(state_copy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let client =
|
||||||
|
serenity::ClientBuilder::new(state.clone().config.discord_token.unwrap(), intents)
|
||||||
|
.framework(framework)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut data = client.data.write().await;
|
||||||
|
data.insert::<BotState>(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(&mut self) -> Result<(), BoxedError> {
|
||||||
|
self.client.start().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self, chat_id: u64, msg: String) -> Result<(), BoxedError> {
|
||||||
|
let builder = CreateMessage::new().content(msg);
|
||||||
|
let message = ChannelId::new(chat_id)
|
||||||
|
.send_message(&self.client.http, builder)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn event_handler(
|
||||||
|
ctx: &serenity::Context,
|
||||||
|
event: &serenity::FullEvent,
|
||||||
|
_framework: poise::FrameworkContext<'_, BotState, BoxedError>,
|
||||||
|
_state: &BotState,
|
||||||
|
) -> Result<(), BoxedError> {
|
||||||
|
match event {
|
||||||
|
serenity::FullEvent::Ready { data_about_bot, .. } => {
|
||||||
|
tracing::info!("discord: Logged in as {}", data_about_bot.user.name);
|
||||||
|
|
||||||
|
// We can use ChannelId directly to send a message to a specific channel; in this case, the
|
||||||
|
// message would be sent to the #testing channel on the discord server.
|
||||||
|
/*let embed = CreateEmbed::new().title("System Resource Load").field(
|
||||||
|
"CPU Load Average",
|
||||||
|
format!("{:.2}%", 10.0),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let builder = CreateMessage::new().embed(embed);
|
||||||
|
let message = ChannelId::new(1145642256443904002)
|
||||||
|
.send_message(&ctx, builder)
|
||||||
|
.await;
|
||||||
|
if let Err(why) = message {
|
||||||
|
eprintln!("Error sending message: {why:?}");
|
||||||
|
};*/
|
||||||
|
}
|
||||||
|
serenity::FullEvent::Message { new_message } => {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let state = data.get_mut::<BotState>().unwrap();
|
||||||
|
println!("{:?}", new_message);
|
||||||
|
|
||||||
|
let author = new_message
|
||||||
|
.author
|
||||||
|
.global_name
|
||||||
|
.clone()
|
||||||
|
.or(Some(new_message.author.name.clone()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(agent) = &state.telegram_agent {
|
||||||
|
agent
|
||||||
|
.send(-4221527632, format!("{}: {}", author, new_message.content))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
78
src/main.rs
78
src/main.rs
@ -1,34 +1,76 @@
|
|||||||
use oscuro_core::{client, config::Config, AppState};
|
mod config;
|
||||||
use std::env;
|
mod discord;
|
||||||
|
mod errors;
|
||||||
|
mod telegram;
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
|
||||||
|
use tokio::signal;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), errors::BoxedError> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_target(false)
|
.with_target(false)
|
||||||
.compact()
|
.compact()
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let mut config = match Config::open(Config::data_dir()?.join("config.toml").as_path()) {
|
tracing::info!("Working directory: {:?}", Config::data_dir()?);
|
||||||
|
|
||||||
|
let config = match Config::open(Config::data_dir()?.join("config.toml").as_path()) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(_) => Config::default(),
|
Err(err) => {
|
||||||
};
|
tracing::debug!("{}", err);
|
||||||
|
tracing::info!("Using default configuration");
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.with_env();
|
||||||
|
|
||||||
if let Ok(token) = env::var("DISCORD_TOKEN") {
|
let mut runset = JoinSet::new();
|
||||||
config.discord_token = token;
|
|
||||||
};
|
|
||||||
|
|
||||||
if config.discord_token.is_empty() {
|
if !config.clone().discord_token.is_some() {
|
||||||
tracing::error!("Missing discord token");
|
tracing::warn!("Missing discord token");
|
||||||
|
} else {
|
||||||
|
let mut discord_client = discord::Client::new(config.clone())
|
||||||
|
.await
|
||||||
|
.expect("Failed to create discord client");
|
||||||
|
|
||||||
|
runset.spawn(async move {
|
||||||
|
|
||||||
|
let res = discord_client.start().await;
|
||||||
|
if let Err(err) = res {
|
||||||
|
tracing::error!("{}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = AppState { config };
|
if !config.clone().telegram_token.is_some() {
|
||||||
|
tracing::warn!("Missing telegram token");
|
||||||
|
} else {
|
||||||
|
let telegram_client = telegram::Client::new(config);
|
||||||
|
|
||||||
client(state)
|
runset.spawn(async move {
|
||||||
.await
|
let res = telegram_client.start().await;
|
||||||
.expect("Failed to create client")
|
if let Err(err) = res {
|
||||||
.start()
|
tracing::error!("{}", err);
|
||||||
.await
|
}
|
||||||
.expect("Failed to start client");
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(res) = runset.join_next().await {
|
||||||
|
if let Err(err) = res {
|
||||||
|
tracing::error!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match signal::ctrl_c().await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Unable to listen for shutdown signal: {}", err);
|
||||||
|
// we also shut down in case of error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
85
src/telegram/mod.rs
Normal file
85
src/telegram/mod.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use teloxide::prelude::*;
|
||||||
|
use teloxide::types::Recipient;
|
||||||
|
use teloxide::utils::command::BotCommands;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::errors;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
async fn main() {
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
/*let http = Http::new("");
|
||||||
|
let webhook = Webhook::from_url(&http, "https://discord.com/api/webhooks/1259860143579987999/whI0ozB5uc17Wdzkb2-HSrVGi8h_MyR2_4eyCsGuGpQN4KcjMhq7rfQH1JIdbD1HNaW_")
|
||||||
|
.await
|
||||||
|
.expect("Replace the webhook with your own");
|
||||||
|
|
||||||
|
let builder = ExecuteWebhook::new().content("hello there").username("Webhook test");
|
||||||
|
webhook.execute(&http, false, builder).await.expect("Could not execute webhook.");
|
||||||
|
*/
|
||||||
|
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||||
|
bot.send_dice(msg.chat.id).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
/*bot.send_message(Recipient::Id(ChatId(-4221527632)), "Heya!")
|
||||||
|
.await
|
||||||
|
.expect("err");*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
bot: Bot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn new(config: Config) -> Self {
|
||||||
|
Self {
|
||||||
|
bot: Bot::new(config.telegram_token.unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(&self) -> Result<(), errors::BoxedError> {
|
||||||
|
Command::repl(self.bot.clone(), event_handler).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self, chat_id: i64, msg: String) -> ResponseResult<()> {
|
||||||
|
self.bot
|
||||||
|
.send_message(Recipient::Id(ChatId(chat_id)), msg)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BotCommands, Clone)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
pub enum Command {
|
||||||
|
#[command()]
|
||||||
|
Dice,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn event_handler(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
|
||||||
|
match cmd {
|
||||||
|
Command::Dice => {
|
||||||
|
let number = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
rng.gen_range(1..21)
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = format!("{} throws {}.", "test", number);
|
||||||
|
let response = match number {
|
||||||
|
20 => format!("{} Critical success.", response),
|
||||||
|
1 => format!("{} Critical failure.", response),
|
||||||
|
_ => response,
|
||||||
|
};
|
||||||
|
// -4221527632
|
||||||
|
|
||||||
|
bot.send_message(Recipient::Id(msg.chat.id), response)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user