Update template with restructured project
This commit is contained in:
parent
f02775f9a0
commit
85c93f6f03
@ -1,8 +1,9 @@
|
||||
.vscode
|
||||
target
|
||||
.env
|
||||
.gitignore
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
renovate.json
|
||||
run.sh
|
||||
|
||||
stuff-to-change.log
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
target
|
||||
.env
|
||||
|
940
Cargo.lock
generated
940
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
Normal file → Executable file
40
Cargo.toml
Normal file → Executable file
@ -3,21 +3,41 @@ name = "rustbot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bb8 = "0.8.5"
|
||||
bb8-postgres = "0.8.1"
|
||||
cargo_toml = "0.20.4"
|
||||
once_cell = "1.19.0"
|
||||
[workspace]
|
||||
members = [
|
||||
"events",
|
||||
"jobs",
|
||||
"library",
|
||||
"tsclient"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
cargo_toml = "0.20.5"
|
||||
poise = "0.6.1"
|
||||
regex = "1.10.6"
|
||||
regex = "1.11.0"
|
||||
serde = "1.0.210"
|
||||
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
||||
reqwest = { version = "0.12.8", features = ["native-tls-vendored"] }
|
||||
|
||||
[dependencies]
|
||||
rustbot_events = { path = "events" }
|
||||
rustbot_lib = { path = "library" }
|
||||
rustbot_tokens = { path = "tsclient" }
|
||||
poise = { workspace = true }
|
||||
rand = "0.8.5"
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
sysinfo = "0.32.0"
|
||||
tokenservice-client = { version = "0.4.0", registry = "gitea" }
|
||||
tokio = { version = "1.39.2", features = ["macros", "signal", "rt-multi-thread"] }
|
||||
tokio-postgres = "0.7.11"
|
||||
time = "0.3.36"
|
||||
tokio = { workspace = true }
|
||||
uptime_lib = "0.3.1"
|
||||
|
||||
[patch.crates-io]
|
||||
poise = { git = "https://github.com/serenity-rs/poise", branch = "serenity-next" }
|
||||
|
||||
[features]
|
||||
production = []
|
||||
production = ["rustbot_lib/production", "rustbot_events/production"]
|
||||
not_ready = ["rustbot_lib/not_ready"]
|
||||
|
||||
[[bin]]
|
||||
name = "rustbot"
|
||||
|
20
Dockerfile
20
Dockerfile
@ -1,24 +1,10 @@
|
||||
FROM rust:1.82-alpine3.20@sha256:466dc9924d265455aa73e72fd9cdac9db69ce6a988e6f0e6baf852db3485d97d AS chef
|
||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||
ARG GIT_HASH
|
||||
ENV GIT_COMMIT_HASH=${GIT_HASH}
|
||||
RUN apk add --no-cache openssl-dev musl-dev
|
||||
RUN cargo install cargo-chef
|
||||
FROM scratch AS base
|
||||
WORKDIR /builder
|
||||
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare
|
||||
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /builder/recipe.json recipe.json
|
||||
RUN cargo chef cook --release
|
||||
COPY . .
|
||||
RUN cargo build --offline -rF production
|
||||
|
||||
FROM alpine:3.20@sha256:e72ad0747b9dc266fca31fb004580d316b6ae5b0fdbbb65f17bbe371a5b24cff
|
||||
LABEL org.opencontainers.image.source="https://git.toast-server.net/toast/Rustbot"
|
||||
RUN apk add --no-cache libgcc
|
||||
WORKDIR /rustbot
|
||||
COPY --from=builder /builder/target/release/rustbot .
|
||||
CMD ./rustbot
|
||||
COPY --from=base /builder/target/x86_64-unknown-linux-musl/release/rustbot .
|
||||
CMD [ "./rustbot" ]
|
||||
|
5
build.sh
Executable file
5
build.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
export GIT_COMMIT_HASH=$(git rev-parse HEAD) && \
|
||||
cargo zigbuild --target x86_64-unknown-linux-musl --locked -rF production && \
|
||||
docker compose build bot
|
@ -1,23 +1,6 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
bot:
|
||||
container_name: rustbot
|
||||
#image: 'git.toast-server.net/toast/rustbot:main'
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
container_name: rustbot-database
|
||||
image: postgres:17.0-alpine3.20@sha256:14195b0729fce792f47ae3c3704d6fd04305826d57af3b01d5b4d004667df174
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 37931:5432/tcp
|
||||
volumes:
|
||||
- /var/lib/docker/volumes/rustbot-database:/var/lib/postgresql/data:rw
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
|
12
events/Cargo.toml
Executable file
12
events/Cargo.toml
Executable file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "rustbot_events"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rustbot_lib = { path = "../library" }
|
||||
poise = { workspace = true }
|
||||
|
||||
[features]
|
||||
production = ["rustbot_lib/production"]
|
||||
not_ready = ["rustbot_lib/not_ready"]
|
33
events/src/events.rs
Executable file
33
events/src/events.rs
Executable file
@ -0,0 +1,33 @@
|
||||
mod ready;
|
||||
mod shards;
|
||||
|
||||
use rustbot_lib::{
|
||||
RustbotError,
|
||||
RustbotData
|
||||
};
|
||||
use poise::{
|
||||
FrameworkContext,
|
||||
serenity_prelude::FullEvent
|
||||
};
|
||||
|
||||
pub const RUSTBOT_EVENT: &str = "RustbotEvent";
|
||||
|
||||
struct EventProcessor<'a> {
|
||||
framework: FrameworkContext<'a, RustbotData, RustbotError>
|
||||
}
|
||||
|
||||
pub async fn processor(
|
||||
framework: FrameworkContext<'_, RustbotData, RustbotError>,
|
||||
event: &FullEvent
|
||||
) -> Result<(), RustbotError> {
|
||||
let processor = EventProcessor { framework };
|
||||
|
||||
match event {
|
||||
FullEvent::Ready { data_about_bot } => processor.on_ready(data_about_bot).await?,
|
||||
FullEvent::ShardsReady { total_shards } => processor.on_shards_ready(total_shards).await?,
|
||||
FullEvent::ShardStageUpdate { event } => processor.on_shards_stageupdate(event).await?,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
67
events/src/events/ready.rs
Executable file
67
events/src/events/ready.rs
Executable file
@ -0,0 +1,67 @@
|
||||
use crate::PoiseFwCtx;
|
||||
use super::{
|
||||
EventProcessor,
|
||||
RUSTBOT_EVENT
|
||||
};
|
||||
|
||||
use rustbot_lib::{
|
||||
RustbotError,
|
||||
utils::{
|
||||
BOT_VERSION,
|
||||
GIT_COMMIT_HASH,
|
||||
GIT_COMMIT_BRANCH
|
||||
},
|
||||
config::BINARY_PROPERTIES
|
||||
};
|
||||
use std::sync::atomic::{
|
||||
AtomicBool,
|
||||
Ordering
|
||||
};
|
||||
use poise::serenity_prelude::{
|
||||
Ready,
|
||||
ChannelId,
|
||||
CreateMessage,
|
||||
CreateEmbed,
|
||||
CreateEmbedAuthor
|
||||
};
|
||||
|
||||
static READY_ONCE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
async fn ready_once(
|
||||
ready: &Ready,
|
||||
framework: PoiseFwCtx<'_>
|
||||
) -> Result<(), RustbotError> {
|
||||
#[cfg(not(feature = "production"))]
|
||||
{
|
||||
println!("{RUSTBOT_EVENT}[Ready:Notice:S{}]: Detected a non-production environment!", framework.serenity_context.shard_id);
|
||||
let gateway = framework.serenity_context.http.get_bot_gateway().await?;
|
||||
let session = gateway.session_start_limit;
|
||||
println!("{RUSTBOT_EVENT}[Ready:Notice:S{}]: Session limit: {}/{}", framework.serenity_context.shard_id, session.remaining, session.total);
|
||||
}
|
||||
|
||||
println!("{RUSTBOT_EVENT}[Ready:S{}]: Build version: {} ({}:{})", framework.serenity_context.shard_id, *BOT_VERSION, GIT_COMMIT_HASH, GIT_COMMIT_BRANCH);
|
||||
println!("{RUSTBOT_EVENT}[Ready:S{}]: Connected to API as {}", framework.serenity_context.shard_id, ready.user.name);
|
||||
|
||||
let message = CreateMessage::new();
|
||||
let ready_embed = CreateEmbed::new()
|
||||
.color(BINARY_PROPERTIES.embed_color)
|
||||
.thumbnail(ready.user.avatar_url().unwrap_or_default())
|
||||
.author(CreateEmbedAuthor::new(format!("{} is ready!", ready.user.name)));
|
||||
|
||||
ChannelId::new(BINARY_PROPERTIES.rustbot_logs).send_message(&framework.serenity_context.http, message.add_embed(ready_embed)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl EventProcessor<'_> {
|
||||
pub async fn on_ready(
|
||||
&self,
|
||||
data_about_bot: &Ready
|
||||
) -> Result<(), RustbotError> {
|
||||
if !READY_ONCE.swap(true, Ordering::Relaxed) {
|
||||
ready_once(data_about_bot, self.framework).await.expect("Failed to call ready_once method");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
29
events/src/events/shards.rs
Executable file
29
events/src/events/shards.rs
Executable file
@ -0,0 +1,29 @@
|
||||
use super::{
|
||||
EventProcessor,
|
||||
RUSTBOT_EVENT
|
||||
};
|
||||
|
||||
use std::num::NonZero;
|
||||
use rustbot_lib::RustbotError;
|
||||
use poise::serenity_prelude::ShardStageUpdateEvent;
|
||||
|
||||
impl EventProcessor<'_> {
|
||||
pub async fn on_shards_ready(
|
||||
&self,
|
||||
total_shards: &NonZero<u16>
|
||||
) -> Result<(), RustbotError> {
|
||||
let shards = if *total_shards == NonZero::new(1).unwrap() { "shard is" } else { "shards are" };
|
||||
println!("{RUSTBOT_EVENT}[ShardsReady]: {total_shards} {shards} ready!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn on_shards_stageupdate(
|
||||
&self,
|
||||
event: &ShardStageUpdateEvent
|
||||
) -> Result<(), RustbotError> {
|
||||
println!("{RUSTBOT_EVENT}[ShardStageUpdate:S{}]: {event:#?}", event.shard_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
45
events/src/lib.rs
Executable file
45
events/src/lib.rs
Executable file
@ -0,0 +1,45 @@
|
||||
pub mod events;
|
||||
|
||||
// use serde_json::json;
|
||||
use rustbot_lib::{
|
||||
RustbotData,
|
||||
RustbotError
|
||||
};
|
||||
use poise::{
|
||||
FrameworkContext,
|
||||
/* serenity_prelude::{
|
||||
Context,
|
||||
WebhookId
|
||||
} */
|
||||
};
|
||||
|
||||
type PoiseFwCtx<'a> = FrameworkContext<'a, RustbotData, RustbotError>;
|
||||
|
||||
/* async fn hook_logger(
|
||||
ctx: &Context,
|
||||
hook_id: WebhookId,
|
||||
token: &str,
|
||||
content: String
|
||||
) -> Result<bool, rustbot_lib::RustbotError> {
|
||||
let current_app = ctx.http.get_current_user().await.unwrap();
|
||||
let bot_avatar = current_app.avatar_url().unwrap();
|
||||
let bot_username = ¤t_app.name;
|
||||
|
||||
if let Err(e) = ctx.http.execute_webhook(
|
||||
hook_id,
|
||||
None,
|
||||
token,
|
||||
true,
|
||||
vec![],
|
||||
&json!({
|
||||
"content": content,
|
||||
"avatar_url": bot_avatar,
|
||||
"username": bot_username
|
||||
})
|
||||
).await {
|
||||
println!("{}[EventWebhook]: Failed to send webhook message: {e}", events::RUSTBOT_EVENT);
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
} */
|
7
jobs/Cargo.toml
Executable file
7
jobs/Cargo.toml
Executable file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "rustbot_jobs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true }
|
3
jobs/src/lib.rs
Executable file
3
jobs/src/lib.rs
Executable file
@ -0,0 +1,3 @@
|
||||
pub mod tasks;
|
||||
|
||||
const RUSTBOT_SCHEDULER: &str = "RustbotScheduler";
|
43
jobs/src/tasks.rs
Executable file
43
jobs/src/tasks.rs
Executable file
@ -0,0 +1,43 @@
|
||||
use crate::RUSTBOT_SCHEDULER;
|
||||
|
||||
use tokio::{
|
||||
task,
|
||||
time::{
|
||||
interval,
|
||||
Duration
|
||||
}
|
||||
};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
future::Future
|
||||
};
|
||||
|
||||
pub struct Scheduler;
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self)
|
||||
}
|
||||
|
||||
pub async fn spawn_job<F, E>(
|
||||
&self,
|
||||
interval_secs: u64,
|
||||
job: Arc<dyn Fn() -> F + Send + Sync + 'static>
|
||||
) where
|
||||
F: Future<Output = Result<(), E>> + Send + 'static,
|
||||
E: std::fmt::Debug
|
||||
{
|
||||
let mut interval = interval(Duration::from_secs(interval_secs));
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let job_clone = job.clone();
|
||||
task::spawn(async move {
|
||||
if let Err(y) = job_clone().await {
|
||||
eprintln!("{RUSTBOT_SCHEDULER}[Job:Error]: {y:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
13
library/Cargo.toml
Executable file
13
library/Cargo.toml
Executable file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "rustbot_lib"
|
||||
version = "0.1.19"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cargo_toml = { workspace = true }
|
||||
poise = { workspace = true }
|
||||
|
||||
[features]
|
||||
production = ["docker"]
|
||||
docker = []
|
||||
not_ready = []
|
37
library/build.rs
Executable file
37
library/build.rs
Executable file
@ -0,0 +1,37 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "production")]
|
||||
{
|
||||
if let Ok(git_commit_hash) = std::env::var("GIT_COMMIT_HASH") {
|
||||
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", &git_commit_hash[..7]);
|
||||
} else {
|
||||
println!("cargo:warning=GIT_COMMIT_HASH not found, falling back to 'not_found'");
|
||||
println!("cargo:rustc-env=GIT_COMMIT_HASH=not_found");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let git_branch = std::process::Command::new("git")
|
||||
.args(["rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.output()
|
||||
.expect("Command execution failed: git");
|
||||
|
||||
if git_branch.status.success() {
|
||||
let git_branch = String::from_utf8(git_branch.stdout).expect("Invalid UTF-8 sequence").trim().to_string();
|
||||
println!("cargo:rustc-env=GIT_COMMIT_BRANCH={}", &git_branch);
|
||||
} else {
|
||||
println!("cargo:warning=GIT_COMMIT_BRANCH not found, falling back to 'not_found'");
|
||||
println!("cargo:rustc-env=GIT_COMMIT_BRANCH=not_found");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let hostname = std::process::Command::new("hostname")
|
||||
.output()
|
||||
.expect("Command execution failed: hostname");
|
||||
|
||||
if hostname.status.success() {
|
||||
let hostname = String::from_utf8(hostname.stdout).expect("Invalid UTF-8 sequence").trim().to_string();
|
||||
println!("cargo:rustc-env=DOCKER_HOSTNAME={}", &hostname);
|
||||
}
|
||||
}
|
||||
}
|
44
library/src/config.rs
Executable file
44
library/src/config.rs
Executable file
@ -0,0 +1,44 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub struct ConfigMeta {
|
||||
pub env: String,
|
||||
pub embed_color: u32,
|
||||
pub rustbot_logs: u64,
|
||||
pub developers: Vec<u64>
|
||||
}
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(ConfigMeta::new);
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(||
|
||||
ConfigMeta::new()
|
||||
.env(String::from("dev"))
|
||||
.embed_color(0xf1d63c)
|
||||
);
|
||||
|
||||
impl ConfigMeta {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
env: String::from("prod"),
|
||||
embed_color: 0xf1d63c,
|
||||
rustbot_logs: 1276874302314254448,
|
||||
developers: vec![
|
||||
190407856527376384 // toast.ts
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Scalable functions below;
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn env(mut self, env: String) -> Self {
|
||||
self.env = env;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn embed_color(mut self, color: u32) -> Self {
|
||||
self.embed_color = color;
|
||||
self
|
||||
}
|
||||
}
|
2
library/src/data.rs
Executable file
2
library/src/data.rs
Executable file
@ -0,0 +1,2 @@
|
||||
#[derive(Debug)]
|
||||
pub struct RustbotData {}
|
7
library/src/lib.rs
Executable file
7
library/src/lib.rs
Executable file
@ -0,0 +1,7 @@
|
||||
pub mod config;
|
||||
mod data;
|
||||
pub use data::RustbotData;
|
||||
pub mod utils;
|
||||
|
||||
pub type RustbotError = Box<dyn std::error::Error + Send + Sync>;
|
||||
pub type RustbotCtx<'a> = poise::Context<'a, RustbotData, RustbotError>;
|
70
library/src/utils.rs
Executable file
70
library/src/utils.rs
Executable file
@ -0,0 +1,70 @@
|
||||
use poise::serenity_prelude::UserId;
|
||||
use cargo_toml::Manifest;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
pub static GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH");
|
||||
#[cfg(not(feature = "production"))]
|
||||
pub static GIT_COMMIT_HASH: &str = "devel";
|
||||
pub static GIT_COMMIT_BRANCH: &str = env!("GIT_COMMIT_BRANCH");
|
||||
|
||||
pub static BOT_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
Manifest::from_str(include_str!("../../Cargo.toml"))
|
||||
.unwrap()
|
||||
.package
|
||||
.unwrap()
|
||||
.version
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub fn format_timestamp(timestamp: i64) -> String {
|
||||
format!("<t:{timestamp}>\n<t:{timestamp}:R>")
|
||||
}
|
||||
|
||||
pub fn mention_dev(ctx: super::RustbotCtx<'_>) -> Option<String> {
|
||||
let devs = super::config::BINARY_PROPERTIES.developers.clone();
|
||||
let app_owners = ctx.framework().options().owners.clone();
|
||||
|
||||
let mut mentions = Vec::new();
|
||||
|
||||
for dev in devs {
|
||||
if app_owners.contains(&UserId::new(dev)) {
|
||||
mentions.push(format!("<@{dev}>"));
|
||||
}
|
||||
}
|
||||
|
||||
if mentions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mentions.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_guild_name(ctx: super::RustbotCtx<'_>) -> String {
|
||||
match ctx.guild() {
|
||||
Some(guild) => guild.name.clone().to_string(),
|
||||
None => String::from("DM")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_duration(secs: u64) -> String {
|
||||
let days = secs / 86400;
|
||||
let hours = (secs % 86400) / 3600;
|
||||
let minutes = (secs % 3600) / 60;
|
||||
let seconds = secs % 60;
|
||||
|
||||
let mut formatted_string = String::new();
|
||||
match (days, hours, minutes, seconds) {
|
||||
(d, _, _, _) if d > 0 => formatted_string.push_str(&format!("{d}d, ")),
|
||||
(_, h, _, _) if h > 0 => formatted_string.push_str(&format!("{h}h, ")),
|
||||
(_, _, m, _) if m > 0 => formatted_string.push_str(&format!("{m}m, ")),
|
||||
(_, _, _, s) if s > 0 => formatted_string.push_str(&format!("{s}s")),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if formatted_string.ends_with(", ") {
|
||||
formatted_string.truncate(formatted_string.len() - 2);
|
||||
}
|
||||
|
||||
formatted_string
|
||||
}
|
3
run.sh
3
run.sh
@ -1,3 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export DOCKER_HOSTNAME=$(hostname)
|
||||
export $(cat .env.bot | xargs)
|
||||
clear && cargo run
|
||||
unset DOCKER_HOSTNAME
|
||||
|
32
src/commands.rs
Normal file → Executable file
32
src/commands.rs
Normal file → Executable file
@ -1,4 +1,28 @@
|
||||
pub mod midi;
|
||||
pub mod ping;
|
||||
pub mod sample;
|
||||
pub mod uptime;
|
||||
mod dev;
|
||||
mod eightball;
|
||||
mod ping;
|
||||
mod uptime;
|
||||
|
||||
pub use dev::dev;
|
||||
pub use eightball::eightball;
|
||||
pub use ping::ping;
|
||||
pub use uptime::uptime;
|
||||
|
||||
type PoiseContext<'a> = rustbot_lib::RustbotCtx<'a>;
|
||||
|
||||
macro_rules! collect {
|
||||
() => {
|
||||
vec![
|
||||
// Developer command(s)
|
||||
commands::dev(),
|
||||
|
||||
// Utility commands
|
||||
commands::ping(),
|
||||
commands::uptime(),
|
||||
|
||||
// Unsorted mess
|
||||
commands::eightball(),
|
||||
]
|
||||
};
|
||||
}
|
||||
pub(crate) use collect;
|
||||
|
50
src/commands/dev.rs
Executable file
50
src/commands/dev.rs
Executable file
@ -0,0 +1,50 @@
|
||||
use crate::RustbotError;
|
||||
use super::PoiseContext;
|
||||
|
||||
/// Developer commands
|
||||
#[poise::command(
|
||||
prefix_command,
|
||||
owners_only,
|
||||
subcommands("deploy", "servers", "shards")
|
||||
)]
|
||||
pub async fn dev(_: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deploy commands to this guild or globally
|
||||
#[poise::command(prefix_command)]
|
||||
async fn deploy(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
poise::builtins::register_application_commands_buttons(ctx).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// View how many servers the bot is in
|
||||
#[poise::command(prefix_command)]
|
||||
async fn servers(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
poise::builtins::servers(ctx).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// View the status of available shards
|
||||
#[poise::command(prefix_command)]
|
||||
async fn shards(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
let shard_runners = ctx.framework().shard_manager().runners.clone();
|
||||
let runners = shard_runners.lock().await;
|
||||
|
||||
let mut shard_info = Vec::new();
|
||||
for (id, runner) in runners.iter() {
|
||||
shard_info.push(format!(
|
||||
"**Shard {}**\n> Heartbeat: {}\n> Status: `{}`",
|
||||
id,
|
||||
match runner.latency {
|
||||
Some(lat) => format!("`{}ms`", lat.as_millis()),
|
||||
None => "Waiting for heartbeat...".to_string()
|
||||
},
|
||||
runner.stage
|
||||
))
|
||||
}
|
||||
|
||||
ctx.reply(shard_info.join("\n\n")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
80
src/commands/eightball.rs
Executable file
80
src/commands/eightball.rs
Executable file
@ -0,0 +1,80 @@
|
||||
use crate::RustbotError;
|
||||
use super::PoiseContext;
|
||||
|
||||
use rustbot_lib::config::BINARY_PROPERTIES;
|
||||
use poise::{
|
||||
serenity_prelude::UserId,
|
||||
builtins::paginate
|
||||
};
|
||||
|
||||
/// Ask the Magic 8-Ball a yes/no question and get an unpredictable answer
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
rename = "8ball"
|
||||
)]
|
||||
pub async fn eightball(
|
||||
ctx: PoiseContext<'_>,
|
||||
#[description = "Your yes/no question"] question: String
|
||||
) -> Result<(), RustbotError> {
|
||||
if question.to_ascii_lowercase().contains("rustbot, show list") {
|
||||
if ctx.author().id == UserId::new(BINARY_PROPERTIES.developers[0]) {
|
||||
let chunks: Vec<String> = RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect();
|
||||
let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
|
||||
paginate(ctx, &pages).await?;
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
ctx.reply("No.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.reply(format!(
|
||||
"> {}\n{}",
|
||||
question,
|
||||
get_random_response()
|
||||
)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const RESPONSES: [&str; 30] = [
|
||||
"Reply hazy. Look it up on Google.", // no
|
||||
"Meh — Figure it out yourself.", // no
|
||||
"I don't know, what do you think?", // no
|
||||
"Yes.", // yes
|
||||
"No.", // no
|
||||
"It is decidedly so", // yes
|
||||
"Signs point to... maybe... depends on... \
|
||||
hold on, let me get my glasses, this is getting \
|
||||
pretty tiny... depends on whether you'd be up \
|
||||
to getting to know your Magic 8-Ball a little better.", // no
|
||||
"Signs point to... ~~yes~~ no.", // no
|
||||
"Why do you want to know the answer? It's obviously a yes.", // yes
|
||||
"Outlook not so good.", // no
|
||||
"Outlook hazy.", // no
|
||||
"What are you, stupid?", // no
|
||||
"How the hell do you not know that?", // no
|
||||
"Really? Making a decision based on what the plastic 8-Ball says? Jesus...", // no
|
||||
"Try asking later...", // no
|
||||
"I don't know, whip out the ouija board and try again?", // no
|
||||
"The answer is yes.", // yes
|
||||
"Yes, actually no. Wait, nevermind.", // no
|
||||
"Maybeee...", // yes
|
||||
"Definitely!", // yes
|
||||
"It is decidedly so.", // yes
|
||||
"My reply is no.", // no
|
||||
"My sources confirms that the answer is no.\n\
|
||||
Source: :sparkles: *i made it up* :sparkles:", // no
|
||||
"As I see it, yes.", // yes
|
||||
"Don't count on it.", // no
|
||||
"Whoa! Why do I have to answer this?", // no
|
||||
"Highly unlikely.", // no
|
||||
"Sure, but with extreme cautions.", // yes
|
||||
"What kind of stupid question is that?? No! I'm not answering that!", // no
|
||||
"Try asking this to a chicken. Probably knows it better than I do!", // no
|
||||
];
|
||||
|
||||
fn get_random_response() -> &'static str {
|
||||
RESPONSES[rand::random::<usize>() % RESPONSES.len()]
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
use crate::{
|
||||
Error,
|
||||
internals::utils::{
|
||||
mention_dev,
|
||||
format_bytes
|
||||
}
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
os::unix::fs::MetadataExt,
|
||||
fs::{
|
||||
write,
|
||||
remove_file,
|
||||
metadata
|
||||
}
|
||||
};
|
||||
use poise::{
|
||||
CreateReply,
|
||||
serenity_prelude::CreateAttachment
|
||||
};
|
||||
|
||||
/// Convert MIDI file to WAV
|
||||
#[poise::command(context_menu_command = "MIDI -> WAV")]
|
||||
pub async fn midi_to_wav(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "MIDI file to be converted"] message: poise::serenity_prelude::Message
|
||||
) -> Result<(), Error> {
|
||||
let re = Regex::new(r"(?i)\.mid$").unwrap();
|
||||
|
||||
if !message.embeds.is_empty() || message.attachments.is_empty() || !re.is_match(&message.attachments[0].filename) {
|
||||
ctx.reply("That ain't a MIDI file! What are you even doing??").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.defer().await?;
|
||||
|
||||
let bytes = match message.attachments[0].download().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(y) => {
|
||||
ctx.send(CreateReply::default()
|
||||
.content(format!(
|
||||
"Download failed, ask {} to check console for more information!",
|
||||
mention_dev(ctx).unwrap_or_default()
|
||||
))
|
||||
)
|
||||
.await.unwrap();
|
||||
|
||||
return Err(Error::from(format!("Failed to download the file: {}", y)))
|
||||
}
|
||||
};
|
||||
|
||||
let midi_path = &message.attachments[0].filename;
|
||||
write(midi_path, bytes)?;
|
||||
|
||||
let wav_path = re.replace(&midi_path, ".wav");
|
||||
|
||||
let sf2_path = "/tmp/FluidR3_GM.sf2";
|
||||
write(sf2_path, include_bytes!("../internals/assets/FluidR3_GM.sf2"))?;
|
||||
|
||||
let output = std::process::Command::new("fluidsynth")
|
||||
.args(&["-ni", sf2_path, midi_path, "-F", &wav_path])
|
||||
.output();
|
||||
|
||||
// Just to add an info to console to tell what the bot is doing when MIDI file is downloaded.
|
||||
println!("Discord[{}:{}]: Processing MIDI file: \"{}\"", ctx.guild().unwrap().name, ctx.command().qualified_name, midi_path);
|
||||
|
||||
match output {
|
||||
Ok(_) => {
|
||||
let reply = ctx.send(CreateReply::default()
|
||||
.attachment(CreateAttachment::path(&*wav_path).await.unwrap())
|
||||
).await;
|
||||
|
||||
if reply.is_err() {
|
||||
println!(
|
||||
"Discord[{}:{}]: Processed file couldn't be uploaded back to Discord channel due to upload limit",
|
||||
ctx.guild().unwrap().name, ctx.command().qualified_name
|
||||
);
|
||||
|
||||
ctx.send(CreateReply::default()
|
||||
.content(format!(
|
||||
"Couldn't upload the processed file (`{}`, `{}`) due to upload limit",
|
||||
&*wav_path, format_bytes(metadata(&*wav_path).unwrap().size())
|
||||
))
|
||||
).await.unwrap();
|
||||
} else if reply.is_ok() {
|
||||
remove_file(midi_path)?;
|
||||
remove_file(&*wav_path)?;
|
||||
}
|
||||
},
|
||||
Err(y) => {
|
||||
ctx.send(CreateReply::default()
|
||||
.content("Command didn't execute successfully, check console for more information!")
|
||||
).await.unwrap();
|
||||
|
||||
return Err(Error::from(format!("Midi conversion failed: {}", y)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
36
src/commands/ping.rs
Normal file → Executable file
36
src/commands/ping.rs
Normal file → Executable file
@ -1,8 +1,36 @@
|
||||
use crate::Error;
|
||||
use crate::RustbotError;
|
||||
use super::PoiseContext;
|
||||
|
||||
/// Check if the bot is alive
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct StatusPage {
|
||||
metrics: Vec<Metrics>
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Metrics {
|
||||
summary: Summary
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Summary {
|
||||
mean: f64
|
||||
}
|
||||
|
||||
/// Check latency of the bot's WS connection and Discord's API
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn ping(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
ctx.reply(format!("Powong! `{:?}`", ctx.ping().await)).await?;
|
||||
pub async fn ping(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
let statuspage: StatusPage = reqwest::get("https://discordstatus.com/metrics-display/5k2rt9f7pmny/day.json")
|
||||
.await.unwrap()
|
||||
.json()
|
||||
.await.unwrap();
|
||||
|
||||
let mut latencies = Vec::new();
|
||||
latencies.push(format!("Discord: `{:.0?}ms`", statuspage.metrics[0].summary.mean));
|
||||
latencies.push(format!("WebSocket: `{:.0?}`", ctx.ping().await));
|
||||
|
||||
ctx.reply(latencies.join("\n")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
use crate::{
|
||||
Error,
|
||||
models::sample::SampleData
|
||||
};
|
||||
|
||||
use poise::CreateReply;
|
||||
|
||||
/// Perform sample CRUD operations in database
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
subcommands("list", "create", "update", "delete"),
|
||||
subcommand_required
|
||||
)]
|
||||
pub async fn sample(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List sample data
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn list(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "ID of the sample data"] id: u64
|
||||
) -> Result<(), Error> {
|
||||
let samples = SampleData::list_data(id).await?;
|
||||
|
||||
let mut response = String::new();
|
||||
for sample in samples {
|
||||
response.push_str(&format!("ID: {}\n", sample.id));
|
||||
response.push_str(&format!("Text: {}\n", sample.text_val));
|
||||
response.push_str(&format!("Int: {}\n", sample.int_val));
|
||||
response.push_str(&format!("Boolean: {}\n\n", sample.boolean_val));
|
||||
}
|
||||
|
||||
ctx.send(CreateReply::default()
|
||||
.content(response)
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create sample data
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn create(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Text value"] text: String,
|
||||
#[description = "Int value"] int: u64,
|
||||
#[description = "Boolean value"] boolean: bool
|
||||
) -> Result<(), Error> {
|
||||
SampleData::create_data(text, int as i64, boolean).await?;
|
||||
|
||||
ctx.send(CreateReply::default().content("Done!")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update sample data
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn update(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "ID of the sample data"] id: u64,
|
||||
#[description = "Text value"] text: String,
|
||||
#[description = "Int value"] int: u64,
|
||||
#[description = "Boolean value"] boolean: bool
|
||||
) -> Result<(), Error> {
|
||||
SampleData::update_data(id, text, int as i64, boolean).await?;
|
||||
|
||||
ctx.send(CreateReply::default().content("Done!")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete sample data
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn delete(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "ID of the sample data"] id: u64
|
||||
) -> Result<(), Error> {
|
||||
SampleData::delete_data(id).await?;
|
||||
|
||||
ctx.send(CreateReply::default().content("Done!")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
88
src/commands/uptime.rs
Normal file → Executable file
88
src/commands/uptime.rs
Normal file → Executable file
@ -1,16 +1,10 @@
|
||||
use crate::{
|
||||
Error,
|
||||
GIT_COMMIT_HASH,
|
||||
internals::utils::{
|
||||
BOT_VERSION,
|
||||
format_duration,
|
||||
concat_message
|
||||
}
|
||||
};
|
||||
use crate::RustbotError;
|
||||
use super::PoiseContext;
|
||||
|
||||
use sysinfo::System;
|
||||
use uptime_lib::get;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::File,
|
||||
path::Path,
|
||||
time::{
|
||||
@ -23,34 +17,54 @@ use std::{
|
||||
BufReader
|
||||
}
|
||||
};
|
||||
use rustbot_lib::utils::{
|
||||
BOT_VERSION,
|
||||
GIT_COMMIT_HASH,
|
||||
GIT_COMMIT_BRANCH,
|
||||
format_duration
|
||||
};
|
||||
|
||||
fn get_os_info() -> String {
|
||||
let path = Path::new("/etc/os-release");
|
||||
let mut name = "BoringOS".to_string();
|
||||
let mut version = "v0.0".to_string();
|
||||
|
||||
if let Ok(file) = File::open(&path) {
|
||||
if let Ok(file) = File::open(path) {
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
if let Ok(line) = line {
|
||||
if line.starts_with("NAME=") {
|
||||
name = line.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string();
|
||||
} else if line.starts_with("VERSION=") {
|
||||
version = line.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string();
|
||||
} else if line.starts_with("VERSION_ID=") {
|
||||
version = line.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string();
|
||||
}
|
||||
let set_value = |s: String| s.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string();
|
||||
reader.lines().map_while(Result::ok).for_each(|line| {
|
||||
match line {
|
||||
l if l.starts_with("NAME=") => name = set_value(l),
|
||||
l if l.starts_with("VERSION=") => version = set_value(l),
|
||||
l if l.starts_with("VERSION_ID=") => version = set_value(l),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
format!("{} {}", name, version)
|
||||
format!("{name} {version}")
|
||||
}
|
||||
|
||||
fn fmt_mem(bytes: u64) -> String {
|
||||
let units = ["B", "KB", "MB", "GB"];
|
||||
let mut bytes = bytes as f64;
|
||||
let mut unit = units[0];
|
||||
|
||||
for &u in &units {
|
||||
if bytes < 1024.0 {
|
||||
unit = u;
|
||||
break;
|
||||
}
|
||||
bytes /= 1024.0;
|
||||
}
|
||||
|
||||
format!("{bytes:.2} {unit}")
|
||||
}
|
||||
|
||||
/// Retrieve host and bot uptimes
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn uptime(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
let _bot = ctx.http().get_current_user().await.unwrap();
|
||||
pub async fn uptime(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
|
||||
let bot = ctx.http().get_current_user().await.unwrap();
|
||||
let mut sys = System::new_all();
|
||||
sys.refresh_all();
|
||||
|
||||
@ -60,7 +74,17 @@ pub async fn uptime(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
// Fetch system's processor
|
||||
let cpu = sys.cpus();
|
||||
|
||||
// Fetch bot's process uptime
|
||||
// Fetch system memory usage
|
||||
let sram = fmt_mem(sys.used_memory());
|
||||
let sram_total = fmt_mem(sys.total_memory());
|
||||
|
||||
// Fetch process memory usage
|
||||
let pram = match sys.process(sysinfo::get_current_pid().unwrap()) {
|
||||
Some(proc) => fmt_mem(proc.memory()),
|
||||
None => String::from("Unavailable")
|
||||
};
|
||||
|
||||
// Fetch process uptime
|
||||
let curr_pid = sysinfo::get_current_pid().unwrap();
|
||||
let now = SystemTime::now();
|
||||
let mut proc_uptime = 0;
|
||||
@ -69,14 +93,22 @@ pub async fn uptime(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
proc_uptime = now.duration_since(time_started).unwrap().as_secs();
|
||||
}
|
||||
|
||||
let stat_msg = vec![
|
||||
format!("**{} {}** `{}`", _bot.name, BOT_VERSION.as_str(), GIT_COMMIT_HASH),
|
||||
// Fetch the node hostname from envvar
|
||||
let docker_node = match var("DOCKER_HOSTNAME") {
|
||||
Ok(h) => h.to_string(),
|
||||
Err(_) => "DOCKER_HOSTNAME is empty!".to_string()
|
||||
};
|
||||
|
||||
let stat_msg = [
|
||||
format!("**{} v{}** `{}:{}`", bot.name, BOT_VERSION.as_str(), GIT_COMMIT_HASH, GIT_COMMIT_BRANCH),
|
||||
format!(">>> System: `{}`", format_duration(sys_uptime)),
|
||||
format!("Process: `{}`", format_duration(proc_uptime)),
|
||||
format!("CPU: `{}`", format!("{}", cpu[0].brand())),
|
||||
format!("Node: `{docker_node}`"),
|
||||
format!("CPU: `{}`", cpu[0].brand()),
|
||||
format!("RAM: `{pram}` (`{sram}/{sram_total}`)"),
|
||||
format!("OS: `{}`", get_os_info())
|
||||
];
|
||||
ctx.reply(concat_message(stat_msg)).await?;
|
||||
ctx.reply(stat_msg.join("\n")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
pub mod database;
|
@ -1,84 +0,0 @@
|
||||
use crate::internals::utils::token_path;
|
||||
|
||||
use bb8::{Pool, PooledConnection};
|
||||
use bb8_postgres::PostgresConnectionManager;
|
||||
use tokio::time::{
|
||||
Duration,
|
||||
sleep
|
||||
};
|
||||
use tokio_postgres::{
|
||||
Client,
|
||||
NoTls,
|
||||
Error,
|
||||
config::Config
|
||||
};
|
||||
use std::{
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
Mutex,
|
||||
LazyLock
|
||||
}
|
||||
};
|
||||
|
||||
pub static DATABASE: LazyLock<Mutex<Option<DatabaseController>>> = LazyLock::new(|| Mutex::new(None));
|
||||
|
||||
pub struct DatabaseController {
|
||||
pub pool: Pool<PostgresConnectionManager<NoTls>>
|
||||
}
|
||||
|
||||
impl DatabaseController {
|
||||
pub async fn new() -> Result<(), Error> {
|
||||
let manager = PostgresConnectionManager::new(Config::from_str(token_path().await.postgres_uri.as_str())?, NoTls);
|
||||
let pool = bb8::Pool::builder().build(manager).await?;
|
||||
let err_name = "Postgres[Error]";
|
||||
|
||||
let pool_clone = pool.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match Self::attempt_connect(&pool_clone).await {
|
||||
Ok(conn) => {
|
||||
println!("Postgres[Info]: Successfully connected");
|
||||
let client: &Client = conn.deref();
|
||||
|
||||
// Sample model
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS sample (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
text_val VARCHAR(255) NOT NULL,
|
||||
int_val BIGINT NOT NULL,
|
||||
boolean_val BOOLEAN NOT NULL
|
||||
);
|
||||
").await.unwrap();
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("{}: {}", err_name, e);
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let controller = Self { pool };
|
||||
*DATABASE.lock().unwrap() = Some(controller);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn attempt_connect<'a>(pool: &'a bb8::Pool<PostgresConnectionManager<NoTls>>) -> Result<PooledConnection<'a, PostgresConnectionManager<NoTls>>, bb8::RunError<Error>> {
|
||||
let mut backoff = 1;
|
||||
loop {
|
||||
match pool.get().await {
|
||||
Ok(conn) => return Ok(conn),
|
||||
Err(e) => {
|
||||
eprintln!("Postgres[ConnError]: {}, retrying in {} seconds", e, backoff);
|
||||
sleep(Duration::from_secs(backoff)).await;
|
||||
if backoff < 64 {
|
||||
backoff *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod tasks;
|
||||
pub mod tsclient;
|
||||
pub mod utils;
|
Binary file not shown.
@ -1,62 +0,0 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub struct ConfigMeta {
|
||||
pub embed_color: i32,
|
||||
pub ready_notify: u64,
|
||||
pub rss_channel: u64,
|
||||
pub rustbot_logs: u64,
|
||||
pub deploy_commands: bool,
|
||||
pub developers: Vec<u64>
|
||||
}
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(|| ConfigMeta::new());
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(||
|
||||
ConfigMeta::new()
|
||||
.embed_color(0xf1d63c)
|
||||
.ready_notify(865673694184996888)
|
||||
.rss_channel(865673694184996888)
|
||||
.deploy_commands(false)
|
||||
);
|
||||
|
||||
impl ConfigMeta {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
embed_color: 0x5a99c7,
|
||||
ready_notify: 865673694184996888,
|
||||
rss_channel: 865673694184996888,
|
||||
rustbot_logs: 1268493237912604672,
|
||||
deploy_commands: false,
|
||||
developers: vec![
|
||||
190407856527376384 // toast.ts
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Scalable functions below;
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn embed_color(mut self, color: i32) -> Self {
|
||||
self.embed_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn ready_notify(mut self, channel_id: u64) -> Self {
|
||||
self.ready_notify = channel_id;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn rss_channel(mut self, channel_id: u64) -> Self {
|
||||
self.rss_channel = channel_id;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn deploy_commands(mut self, deploy: bool) -> Self {
|
||||
self.deploy_commands = deploy;
|
||||
self
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
pub mod sample;
|
||||
|
||||
fn task_info(name: &str, message: &str) {
|
||||
println!("{}", format!("TaskScheduler[{}]: {}", name, message));
|
||||
}
|
||||
|
||||
fn task_err(name: &str, message: &str) {
|
||||
eprintln!("{}", format!("TaskScheduler[{}:Error]: {}", name, message));
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
use crate::Error;
|
||||
use super::{
|
||||
super::config::BINARY_PROPERTIES,
|
||||
task_info,
|
||||
task_err
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
use poise::serenity_prelude::{
|
||||
Context,
|
||||
ChannelId,
|
||||
CreateMessage
|
||||
};
|
||||
use tokio::time::{
|
||||
Duration,
|
||||
interval
|
||||
};
|
||||
|
||||
pub async fn sample(ctx: Arc<Context>) -> Result<(), Error> {
|
||||
let task_name = "SampleTask";
|
||||
let mut interval = interval(Duration::from_secs(10));
|
||||
task_info(&task_name, "Task loaded!");
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
task_info(&task_name, "Task running!");
|
||||
|
||||
if BINARY_PROPERTIES.rss_channel == 0 {
|
||||
task_err(&task_name, "RSS channel ID is not set!");
|
||||
ChannelId::new(BINARY_PROPERTIES.rustbot_logs).send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new().content("RSS channel ID is not set!")
|
||||
).await.unwrap();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ChannelId::new(BINARY_PROPERTIES.rss_channel).send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new().content("This is a sample message executed by a task!")
|
||||
).await.unwrap();
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
use tokenservice_client::{
|
||||
TokenService,
|
||||
TokenServiceApi
|
||||
};
|
||||
|
||||
pub struct TSClient(TokenService);
|
||||
|
||||
impl TSClient {
|
||||
pub fn new() -> Self {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let service = if args.len() > 1 { &args[1] } else { "pgbot" };
|
||||
Self(TokenService::new(service))
|
||||
}
|
||||
|
||||
pub async fn get(&self) -> Result<TokenServiceApi, crate::Error> {
|
||||
match self.0.connect().await {
|
||||
Ok(api) => Ok(api),
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
use poise::serenity_prelude::UserId;
|
||||
use std::sync::LazyLock;
|
||||
use tokio::sync::Mutex;
|
||||
use tokenservice_client::TokenServiceApi;
|
||||
use super::tsclient::TSClient;
|
||||
|
||||
pub static BOT_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
let cargo_version = cargo_toml::Manifest::from_str(&include_str!("../../Cargo.toml"))
|
||||
.unwrap()
|
||||
.package
|
||||
.unwrap()
|
||||
.version
|
||||
.unwrap();
|
||||
format!("v{}", cargo_version)
|
||||
});
|
||||
|
||||
static TSCLIENT: LazyLock<Mutex<TSClient>> = LazyLock::new(|| Mutex::new(TSClient::new()));
|
||||
|
||||
pub async fn token_path() -> TokenServiceApi {
|
||||
TSCLIENT.lock().await.get().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn concat_message(messages: Vec<String>) -> String {
|
||||
messages.join("\n")
|
||||
}
|
||||
|
||||
pub fn mention_dev(ctx: poise::Context<'_, (), crate::Error>) -> Option<String> {
|
||||
let devs = super::config::BINARY_PROPERTIES.developers.clone();
|
||||
let app_owners = ctx.framework().options().owners.clone();
|
||||
|
||||
let mut mentions = Vec::new();
|
||||
|
||||
for dev in devs {
|
||||
if app_owners.contains(&UserId::new(dev)) {
|
||||
mentions.push(format!("<@{}>", dev));
|
||||
}
|
||||
}
|
||||
|
||||
if mentions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mentions.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_duration(secs: u64) -> String {
|
||||
let days = secs / 86400;
|
||||
let hours = (secs % 86400) / 3600;
|
||||
let minutes = (secs % 3600) / 60;
|
||||
let seconds = secs % 60;
|
||||
|
||||
let mut formatted_string = String::new();
|
||||
if days > 0 {
|
||||
formatted_string.push_str(&format!("{}d, ", days));
|
||||
}
|
||||
if hours > 0 || days > 0 {
|
||||
formatted_string.push_str(&format!("{}h, ", hours));
|
||||
}
|
||||
if minutes > 0 || hours > 0 {
|
||||
formatted_string.push_str(&format!("{}m, ", minutes));
|
||||
}
|
||||
formatted_string.push_str(&format!("{}s", seconds));
|
||||
|
||||
formatted_string
|
||||
}
|
||||
|
||||
pub fn format_bytes(bytes: u64) -> String {
|
||||
let units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||
let mut value = bytes as f64;
|
||||
let mut unit = units[0];
|
||||
|
||||
for &u in &units[1..] {
|
||||
if value < 1024.0 {
|
||||
break;
|
||||
}
|
||||
|
||||
value /= 1024.0;
|
||||
unit = u;
|
||||
}
|
||||
|
||||
if unit == "B" {
|
||||
format!("{}{}", value, unit)
|
||||
} else {
|
||||
format!("{:.2}{}", value, unit)
|
||||
}
|
||||
}
|
260
src/main.rs
Normal file → Executable file
260
src/main.rs
Normal file → Executable file
@ -1,187 +1,68 @@
|
||||
mod commands;
|
||||
mod controllers;
|
||||
mod models;
|
||||
mod internals;
|
||||
// https://cdn.toast-server.net/RustFSHiearchy.png
|
||||
// Using the new filesystem hierarchy
|
||||
|
||||
use crate::{
|
||||
internals::{
|
||||
utils::{
|
||||
BOT_VERSION,
|
||||
token_path,
|
||||
mention_dev
|
||||
},
|
||||
config::BINARY_PROPERTIES
|
||||
},
|
||||
controllers::database::DatabaseController
|
||||
};
|
||||
|
||||
use std::{
|
||||
thread::current,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{
|
||||
AtomicBool,
|
||||
Ordering
|
||||
}
|
||||
}
|
||||
};
|
||||
use rustbot_tokens::token_path;
|
||||
use poise::serenity_prelude::{
|
||||
builder::{
|
||||
CreateMessage,
|
||||
CreateEmbed,
|
||||
CreateEmbedAuthor
|
||||
},
|
||||
Ready,
|
||||
Context,
|
||||
FullEvent,
|
||||
builder::CreateAllowedMentions,
|
||||
ClientBuilder,
|
||||
ChannelId,
|
||||
Command,
|
||||
ActivityData,
|
||||
GatewayIntents
|
||||
};
|
||||
|
||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
static TASK_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
pub static GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH");
|
||||
#[cfg(not(feature = "production"))]
|
||||
pub static GIT_COMMIT_HASH: &str = "devel";
|
||||
|
||||
async fn on_ready(
|
||||
ctx: &Context,
|
||||
ready: &Ready,
|
||||
framework: &poise::Framework<(), Error>
|
||||
) -> Result<(), Error> {
|
||||
#[cfg(not(feature = "production"))]
|
||||
{
|
||||
println!("Event[Ready][Notice]: Detected a non-production environment!");
|
||||
let gateway = ctx.http.get_bot_gateway().await?;
|
||||
let session = gateway.session_start_limit;
|
||||
println!("Event[Ready][Notice]: Session limit: {}/{}", session.remaining, session.total);
|
||||
}
|
||||
|
||||
println!("Event[Ready]: Build version: {} ({})", BOT_VERSION.to_string(), GIT_COMMIT_HASH);
|
||||
println!("Event[Ready]: Connected to API as {}", ready.user.name);
|
||||
|
||||
let message = CreateMessage::new();
|
||||
let ready_embed = CreateEmbed::new()
|
||||
.color(BINARY_PROPERTIES.embed_color)
|
||||
.thumbnail(ready.user.avatar_url().unwrap_or_default())
|
||||
.author(CreateEmbedAuthor::new(format!("{} is ready!", ready.user.name)));
|
||||
|
||||
ChannelId::new(BINARY_PROPERTIES.ready_notify).send_message(&ctx.http, message.add_embed(ready_embed)).await?;
|
||||
|
||||
if BINARY_PROPERTIES.deploy_commands {
|
||||
let builder = poise::builtins::create_application_commands(&framework.options().commands);
|
||||
let commands = Command::set_global_commands(&ctx.http, builder).await;
|
||||
let mut commands_deployed = std::collections::HashSet::new();
|
||||
|
||||
match commands {
|
||||
Ok(cmdmap) => for command in cmdmap.iter() {
|
||||
commands_deployed.insert(command.name.clone());
|
||||
},
|
||||
Err(y) => eprintln!("Error registering commands: {:?}", y)
|
||||
}
|
||||
|
||||
if commands_deployed.len() > 0 {
|
||||
println!("Event[Ready]: Deployed the commands globally:\n- {}", commands_deployed.into_iter().collect::<Vec<_>>().join("\n- "));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn event_processor(
|
||||
ctx: &Context,
|
||||
event: &FullEvent,
|
||||
framework: poise::FrameworkContext<'_, (), Error>
|
||||
) -> Result<(), Error> {
|
||||
match event {
|
||||
FullEvent::Ratelimit { data } => {
|
||||
println!("Event[Ratelimit]: {:#?}", data);
|
||||
}
|
||||
FullEvent::Message { new_message } => {
|
||||
if new_message.author.bot || !new_message.guild_id.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if new_message.content.to_lowercase().starts_with("deploy") && new_message.author.id == BINARY_PROPERTIES.developers[0] {
|
||||
let builder = poise::builtins::create_application_commands(&framework.options().commands);
|
||||
let commands = Command::set_global_commands(&ctx.http, builder).await;
|
||||
let mut commands_deployed = std::collections::HashSet::new();
|
||||
|
||||
match commands {
|
||||
Ok(cmdmap) => for command in cmdmap.iter() {
|
||||
commands_deployed.insert(command.name.clone());
|
||||
},
|
||||
Err(y) => {
|
||||
eprintln!("Error registering commands: {:?}", y);
|
||||
new_message.reply(&ctx.http, "Deployment failed, check console for more details!").await?;
|
||||
}
|
||||
}
|
||||
|
||||
if commands_deployed.len() > 0 {
|
||||
new_message.reply(&ctx.http, format!(
|
||||
"Deployed the commands globally:\n- {}",
|
||||
commands_deployed.into_iter().collect::<Vec<_>>().join("\n- ")
|
||||
)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
FullEvent::Ready { .. } => {
|
||||
let thread_id = format!("{:?}", current().id());
|
||||
let thread_num: String = thread_id.chars().filter(|c| c.is_digit(10)).collect();
|
||||
println!("Event[Ready]: Task Scheduler operating on thread {}", thread_num);
|
||||
|
||||
let ctx = Arc::new(ctx.clone());
|
||||
|
||||
if !TASK_RUNNING.load(Ordering::SeqCst) {
|
||||
TASK_RUNNING.store(true, Ordering::SeqCst);
|
||||
|
||||
tokio::spawn(async move {
|
||||
match internals::tasks::sample::sample(ctx).await {
|
||||
Ok(_) => {},
|
||||
Err(y) => {
|
||||
eprintln!("TaskScheduler[Main:Sample:Error]: Task execution failed: {}", y);
|
||||
if let Some(source) = y.source() {
|
||||
eprintln!("TaskScheduler[Main:Sample:Error]: Task execution failed caused by: {:#?}", source);
|
||||
}
|
||||
}
|
||||
}
|
||||
TASK_RUNNING.store(false, Ordering::SeqCst);
|
||||
});
|
||||
} else {
|
||||
println!("TaskScheduler[Main:Notice]: Another thread is already running, ignoring");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
use rustbot_lib::{
|
||||
utils::{
|
||||
mention_dev,
|
||||
get_guild_name
|
||||
},
|
||||
RustbotError,
|
||||
RustbotData,
|
||||
config::BINARY_PROPERTIES
|
||||
};
|
||||
use rustbot_events::events::processor;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
borrow::Cow
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
DatabaseController::new().await.expect("Error initializing database");
|
||||
let prefix = if BINARY_PROPERTIES.env.contains("prod") {
|
||||
Some(Cow::Borrowed("pg."))
|
||||
} else {
|
||||
Some(Cow::Borrowed("pg!"))
|
||||
};
|
||||
|
||||
let commands = commands::collect!();
|
||||
let framework = poise::Framework::builder()
|
||||
.options(poise::FrameworkOptions {
|
||||
commands: vec![
|
||||
commands::ping::ping(),
|
||||
commands::sample::sample(),
|
||||
commands::midi::midi_to_wav(),
|
||||
commands::uptime::uptime()
|
||||
],
|
||||
commands,
|
||||
pre_command: |ctx| Box::pin(async move {
|
||||
let get_guild_name = match ctx.guild() {
|
||||
Some(guild) => guild.name.clone(),
|
||||
None => String::from("Direct Message")
|
||||
let get_guild_channel_name = match ctx.guild_channel().await {
|
||||
Some(channel) => format!("in #{}", channel.name.clone()),
|
||||
None => String::from("")
|
||||
};
|
||||
println!("Discord[{}]: {} ran /{}", get_guild_name, ctx.author().name, ctx.command().qualified_name);
|
||||
let prefix = match ctx.command().prefix_action {
|
||||
Some(_) => ctx.framework().options.prefix_options.prefix.as_ref().unwrap(),
|
||||
None => "/"
|
||||
};
|
||||
|
||||
println!(
|
||||
"Discord[{}:S{}]: {} ran {}{} {}",
|
||||
get_guild_name(ctx),
|
||||
ctx.serenity_context().shard_id,
|
||||
ctx.author().name,
|
||||
prefix,
|
||||
ctx.command().qualified_name,
|
||||
get_guild_channel_name);
|
||||
}),
|
||||
prefix_options: poise::PrefixFrameworkOptions {
|
||||
prefix,
|
||||
case_insensitive_commands: true,
|
||||
ignore_bots: true,
|
||||
execute_self_messages: false,
|
||||
mention_as_prefix: false,
|
||||
..Default::default()
|
||||
},
|
||||
on_error: |error| Box::pin(async move {
|
||||
match error {
|
||||
poise::FrameworkError::Command { error, ctx, .. } => {
|
||||
@ -192,32 +73,63 @@ async fn main() {
|
||||
)).await.expect("Error sending message");
|
||||
},
|
||||
poise::FrameworkError::EventHandler { error, event, .. } => println!("PoiseEventHandlerError({}): {}", event.snake_case_name(), error),
|
||||
poise::FrameworkError::Setup { error, .. } => println!("PoiseSetupError: {}", error),
|
||||
poise::FrameworkError::NotAnOwner { ctx, .. } => {
|
||||
println!("PoiseNotAnOwner: {} tried to execute a developer-level command ({})", ctx.author().name, ctx.command().qualified_name);
|
||||
ctx.reply("Whoa, you discovered a hidden command! Too bad, I can't allow you to execute it as you're not my creator.").await.expect("Error sending message");
|
||||
},
|
||||
poise::FrameworkError::UnknownInteraction { interaction, .. } => println!(
|
||||
"PoiseUnknownInteractionError: {} tried to execute an unknown interaction ({})",
|
||||
interaction.user.name,
|
||||
interaction.data.name
|
||||
),
|
||||
other => println!("PoiseOtherError: {}", other)
|
||||
poise::FrameworkError::UnknownCommand { msg, .. } => println!(
|
||||
"PoiseUnknownCommandError: {} tried to execute an unknown command ({})",
|
||||
msg.author.name,
|
||||
msg.content
|
||||
),
|
||||
poise::FrameworkError::ArgumentParse { ctx, error, .. } => {
|
||||
println!("PoiseArgumentParseError: {}", error);
|
||||
ctx.reply(format!("Error parsing argument(s): {error}")).await.expect("Error sending message");
|
||||
},
|
||||
poise::FrameworkError::CommandPanic { ctx, payload, .. } => {
|
||||
if let Some(payload) = payload.clone() {
|
||||
println!("PoiseCommandPanic: {payload}");
|
||||
ctx.reply(format!(
|
||||
"The command panicked, please tell my developer about this!\n**Error:**```\n{payload}\n```"
|
||||
)).await.expect("Error sending message");
|
||||
} else {
|
||||
println!("PoiseCommandPanic: No payload provided");
|
||||
let uh_oh = [
|
||||
"Well, this is concerning... Hopefully you notified my developer about this!",
|
||||
"The command panicked, but didn't leave any trace behind... Suspicious!",
|
||||
].join("\n");
|
||||
ctx.reply(uh_oh).await.expect("Error sending message");
|
||||
}
|
||||
},
|
||||
other => println!("PoiseOtherError: {other}")
|
||||
}
|
||||
}),
|
||||
allowed_mentions: Some(CreateAllowedMentions::default().empty_users()),
|
||||
initialize_owners: true,
|
||||
event_handler: |ctx, event, framework, _| Box::pin(event_processor(ctx, event, framework)),
|
||||
skip_checks_for_owners: true,
|
||||
event_handler: |framework, event| Box::pin(processor(framework, event)),
|
||||
..Default::default()
|
||||
})
|
||||
.setup(|ctx, ready, framework| Box::pin(on_ready(ctx, ready, framework)))
|
||||
.build();
|
||||
|
||||
let mut client = ClientBuilder::new(
|
||||
token_path().await.main,
|
||||
&token_path().await.main,
|
||||
GatewayIntents::GUILDS
|
||||
| GatewayIntents::MESSAGE_CONTENT
|
||||
| GatewayIntents::GUILD_MESSAGES
|
||||
| GatewayIntents::DIRECT_MESSAGES
|
||||
| GatewayIntents::MESSAGE_CONTENT
|
||||
)
|
||||
.framework(framework)
|
||||
.data(Arc::new(RustbotData {}))
|
||||
.activity(ActivityData::custom("nep nep!"))
|
||||
.await.expect("Error creating client");
|
||||
|
||||
if let Err(why) = client.start().await {
|
||||
if let Err(why) = client.start_autosharded().await {
|
||||
println!("Error starting client: {:#?}", why);
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
pub mod sample;
|
@ -1,96 +0,0 @@
|
||||
use crate::controllers::database::DATABASE;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct SampleData {
|
||||
pub id: i64,
|
||||
pub text_val: String,
|
||||
pub int_val: i64,
|
||||
pub boolean_val: bool
|
||||
}
|
||||
|
||||
impl SampleData {
|
||||
pub async fn list_data(id: u64) -> Result<Vec<Self>, tokio_postgres::Error> {
|
||||
let pool = {
|
||||
let db = DATABASE.lock().unwrap();
|
||||
let controller = db.as_ref().unwrap();
|
||||
controller.pool.clone()
|
||||
};
|
||||
let conn = pool.get().await.unwrap();
|
||||
let client = conn.deref();
|
||||
let rows = client.query("
|
||||
SELECT * FROM sample
|
||||
WHERE id = $1
|
||||
", &[&(id as i64)]).await?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
for row in rows {
|
||||
data.push(Self {
|
||||
id: row.get("id"),
|
||||
text_val: row.get("text_val"),
|
||||
int_val: row.get("int_val"),
|
||||
boolean_val: row.get("boolean_val")
|
||||
});
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn create_data(
|
||||
text: String,
|
||||
int: i64,
|
||||
boolean: bool
|
||||
) -> Result<(), tokio_postgres::Error> {
|
||||
let pool = {
|
||||
let db = DATABASE.lock().unwrap();
|
||||
let controller = db.as_ref().unwrap();
|
||||
controller.pool.clone()
|
||||
};
|
||||
let conn = pool.get().await.unwrap();
|
||||
let client = conn.deref();
|
||||
client.execute("
|
||||
INSERT INTO sample (text_val, int_val, boolean_val)
|
||||
VALUES ($1, $2, $3)
|
||||
", &[&text, &int, &boolean]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_data(
|
||||
id: u64,
|
||||
text: String,
|
||||
int: i64,
|
||||
boolean: bool
|
||||
) -> Result<(), tokio_postgres::Error> {
|
||||
let pool = {
|
||||
let db = DATABASE.lock().unwrap();
|
||||
let controller = db.as_ref().unwrap();
|
||||
controller.pool.clone()
|
||||
};
|
||||
let conn = pool.get().await.unwrap();
|
||||
let client = conn.deref();
|
||||
client.execute("
|
||||
UPDATE sample
|
||||
SET text_val = $1, int_val = $2, boolean_val = $3
|
||||
WHERE id = $4
|
||||
", &[&text, &int, &boolean, &(id as i64)]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_data(id: u64) -> Result<(), tokio_postgres::Error> {
|
||||
let pool = {
|
||||
let db = DATABASE.lock().unwrap();
|
||||
let controller = db.as_ref().unwrap();
|
||||
controller.pool.clone()
|
||||
};
|
||||
let conn = pool.get().await.unwrap();
|
||||
let client = conn.deref();
|
||||
client.execute("
|
||||
DELETE FROM sample
|
||||
WHERE id = $1
|
||||
", &[&(id as i64)]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
11
stuff-to-change.log
Normal file
11
stuff-to-change.log
Normal file
@ -0,0 +1,11 @@
|
||||
Only things that are needing to be changed before deploying this template to real-world use;
|
||||
- The project name (Rustbot)
|
||||
- Dockerfile image tag and its service in compose file
|
||||
- events (rustbot_events), and its references
|
||||
- jobs (rustbot_jobs), and its references
|
||||
- library (rustbot_lib), and its references
|
||||
- tsclient (rustbot_tokens), and its references
|
||||
|
||||
Search by Rustbot in its usual form (all-caps, pascalcase, and such) to find all the references that need to be changed too.
|
||||
|
||||
(and delete this file too!)
|
8
tsclient/Cargo.toml
Executable file
8
tsclient/Cargo.toml
Executable file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "rustbot_tokens"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokenservice-client = { version = "0.4.1", registry = "gitea" }
|
||||
tokio = { workspace = true }
|
35
tsclient/src/lib.rs
Executable file
35
tsclient/src/lib.rs
Executable file
@ -0,0 +1,35 @@
|
||||
use std::sync::LazyLock;
|
||||
use tokio::sync::Mutex;
|
||||
use tokenservice_client::{
|
||||
TokenService,
|
||||
TokenServiceApi
|
||||
};
|
||||
|
||||
pub struct TSClient(TokenService);
|
||||
|
||||
impl Default for TSClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TSClient {
|
||||
pub fn new() -> Self {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let service = if args.len() > 1 { &args[1] } else { "pgbot" };
|
||||
Self(TokenService::new(service))
|
||||
}
|
||||
|
||||
pub async fn get(&self) -> Result<TokenServiceApi, Box<dyn std::error::Error + Send + Sync>> {
|
||||
match self.0.connect().await {
|
||||
Ok(api) => Ok(api),
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TSCLIENT: LazyLock<Mutex<TSClient>> = LazyLock::new(|| Mutex::new(TSClient::new()));
|
||||
|
||||
pub async fn token_path() -> TokenServiceApi {
|
||||
TSCLIENT.lock().await.get().await.unwrap()
|
||||
}
|
Loading…
Reference in New Issue
Block a user