Skip to content

Configurable messages content #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ twilight-validate = "0.15"

serde_json = "1.0.78"
serde_repr = "0.1.9"
serde = "1.0.136"
toml = "0.7.3"
serde = { version = "1.0", features = ["rc"] }
tera = "1"

async-trait = "0.1.57"
futures-util = "0.3.19"
tokio = "1.16.1"
tokio = { version = "1.16.1", features = ["fs"] }

mongodb = "2.1.0"
redis = "0.21.6"
Expand All @@ -33,4 +35,5 @@ chrono = "0.4.19"
reqwest = "0.11.9"
regex = "1.5.4"
dashmap = "5.2.0"
hex = "0.4.3"
hex = "0.4.3"
console = "0.15"
37 changes: 37 additions & 0 deletions messages.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
["commands.setup"]
content = """
**The server setup is not completed yet or commands are not synced**

To complete a setup open the dashboard https://custom.fail/setup?guild={{ interaction.guild_id }}
To sync commands open a server settings https://custom.fail/servers/{{ interaction.guild_id} }
"""
ephemeral = true

["commands.clear"]
content = "Succesfully cleared {{ cleared }} from {{ amount }} messages"
ephemeral = true

[["commands.top.all".embeds]]
title = "Top of the {{ weekOrDay }} users"
description = """
{% set emotes = [":first_place:", ":second_place:", ":third_place:"] %}
{% for rank in leaderboard %}
{% set i = loop.index - 1 %}
{{ emotes[i] }} > <@{{rank[0]}}> ({{ rank[1] }})
{% endfor %}
"""

[["commands.top.me".embeds]]
title = "Top of the {{ weekOrDay }} for {{ interaction.user.name }}"
description = """
You are **{{ userPosition + 1 }}** with **{{ userScore }}**
{% for rank in leaderboard %}
{% set i = loop.index - 1 %}
{% set rankScore = rank[1] %}
{% set toRank = userScore - rankScore %}
{%
{% endfor %}
"""

[["case.non-server".embeds]]
title = "test"
70 changes: 70 additions & 0 deletions src/assets/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
use twilight_model::channel::message::embed::{EmbedAuthor, EmbedFooter};

#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub enum CaseMessageType {
#[serde(rename = "non-server")]
NonServer,
#[serde(rename = "moderation-log")]
ModerationLog,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Message {
pub content: Option<String>,
#[serde(default)]
pub ephemeral: bool,
#[serde(default)]
pub embeds: Vec<Embed>,
#[serde(default, rename = "add-case")]
pub add_case: Option<CaseMessageType>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Embed {
pub title: Option<String>,
pub description: Option<String>,
pub thumbnail: Option<String>,
pub footer: Option<TextIcon>,
#[serde(default)]
pub fields: Vec<EmbedField>,
pub author: Option<TextIcon>,
pub color: Option<u32>,
pub image: Option<String>,
pub video: Option<String>,
pub url: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct EmbedField {
pub inline: Option<String>,
pub name: String,
pub value: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TextIcon {
pub text: String,
pub icon_url: Option<String>,
}

impl Into<EmbedAuthor> for TextIcon {
fn into(self) -> EmbedAuthor {
EmbedAuthor {
icon_url: self.icon_url.to_owned(),
name: self.text,
proxy_icon_url: self.icon_url,
url: None,
}
}
}

impl Into<EmbedFooter> for TextIcon {
fn into(self) -> EmbedFooter {
EmbedFooter {
icon_url: self.icon_url.to_owned(),
proxy_icon_url: self.icon_url,
text: self.text,
}
}
}
95 changes: 95 additions & 0 deletions src/assets/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::{collections::HashMap, sync::Arc};

use crate::utils::cli;
use serde::{Deserialize, Serialize};
use tokio::fs;
use tokio::sync::RwLock;

use self::message::Message;

mod message;
pub mod render;

pub type Asset = HashMap<String, Arc<Message>>;

pub struct AssetsManager {
pub default: Asset,
pub custom: RwLock<HashMap<String, Arc<Asset>>>,
}

const URL: &str = "https://raw.githubusercontent.com/oceaann/custom/dev/messages.yaml";
async fn fetch_default_asset() -> Option<String> {
match reqwest::get(URL).await {
Ok(res) => res.text().await.ok(),
Err(err) => {
eprintln!("Cannot fetch assets - applying defaults (empty values): {err}");
None
}
}
}

fn parse_asset_config(asset: &str) -> Result<Asset, toml::de::Error> {
toml::from_str(asset)
}

const ASSETS_CONFIG_PATH: &str = "./messages.toml";
const CANNOT_PARSE_FETCHED_CONFIG: &str = "Cannot parse auto-downloaded message configuration";

async fn load_default(path: Option<String>) -> Asset {
let path = path.unwrap_or(ASSETS_CONFIG_PATH.to_string());
let file = fs::read_to_string(path.to_owned()).await;

match file {
Ok(file) => {
parse_asset_config(file.as_str()).expect(format!("Cannot parse {path}").as_str())
}
Err(error) => {
if error.kind() == std::io::ErrorKind::NotFound {
if cli::confirm(
"Cannot load file {path} do you want to fetch the recommended configuration",
) {
println!("Fetching {URL}");
fetch_default_asset()
.await
.map(|asset| {
parse_asset_config(asset.as_str()).expect(CANNOT_PARSE_FETCHED_CONFIG)
})
.unwrap_or_default()
} else {
Default::default()
}
} else {
eprintln!(
"Cannot load assets with default messages: {}",
error.to_string()
);
Default::default()
}
}
}
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GuildAssets(pub Vec<String>);

impl AssetsManager {
pub async fn new() -> Self {
Self {
default: load_default(None).await,
custom: Default::default(),
}
}
}

#[macro_export]
macro_rules! render_context {
($([$name: expr, $value: expr]),* ) => {
{
let mut ctx = tera::Context::new();
$(
ctx.insert($name, $value);
)*
ctx
}
};
}
Loading