Skip to content

Commit

Permalink
feat: template channels™
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanccn committed Jan 17, 2024
1 parent 3d42267 commit 3f9a8c1
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 0 deletions.
55 changes: 55 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ color-eyre = "0.6.2"
dotenvy = "0.15.7"
env_logger = "0.10.1"
humantime = "2.1.0"
indexmap = { version = "2.1.0", features = ["serde"] }
log = "0.4.20"
nanoid = "0.4.0"
num-traits = "0.2.17"
Expand All @@ -32,6 +33,7 @@ serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.109"
sysinfo = "0.30.5"
tokio = { version = "1.35.0", features = ["full"] }
toml = "0.8.8"

[profile.release]
opt-level = "z"
Expand Down
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod say;
mod self_timeout;
mod shiggy;
mod sysinfo;
mod template_channel;
mod translate;
mod version;

Expand All @@ -31,6 +32,7 @@ pub fn to_vec() -> Vec<
self_timeout::transparency(),
shiggy::shiggy(),
sysinfo::sysinfo(),
template_channel::template_channel(),
translate::translate(),
version::version(),
]
Expand Down
50 changes: 50 additions & 0 deletions src/commands/template_channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use color_eyre::eyre::Result;
use poise::{
serenity_prelude::{futures::StreamExt, ChannelId, CreateEmbed},
CreateReply,
};

use crate::{reqwest_client::HTTP, template_channel::Config as TemplateChannelConfig, Context};

/// Apply a channel template from a URL to a channel
#[poise::command(rename = "template-channel", slash_command, guild_only, ephemeral)]
pub async fn template_channel(
ctx: Context<'_>,
#[description = "The URL to fetch the template from"] url: String,
#[description = "The channel to apply the template to"] channel: ChannelId,
#[description = "Whether or not to clear the channel (default true)"] clear: Option<bool>,
) -> Result<()> {
let clear = clear.unwrap_or(true);
ctx.defer_ephemeral().await?;

let source = HTTP.get(&url).send().await?.text().await?;
let data = TemplateChannelConfig::parse(&source)?;
let messages = data.to_messages();

if clear {
let mut message_iter = channel.messages_iter(&ctx).boxed();
while let Some(message) = message_iter.next().await {
if let Ok(message) = message {
message.delete(&ctx).await?;
}
}
}

for m in messages {
channel.send_message(&ctx, m).await?;
}

ctx.send(
CreateReply::default().embed(
CreateEmbed::default()
.title("Applied channel template!")
.field("URL", format!("`{url}`"), false)
.field("Channel", format!("<#{channel}>"), false)
.field("Components", format!("{}", data.components.len()), false)
.color(0x22d3ee),
),
)
.await?;

Ok(())
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod handlers;
mod reqwest_client;
mod starboard;
mod storage;
mod template_channel;
mod utils;

#[allow(clippy::too_many_lines)]
Expand Down
143 changes: 143 additions & 0 deletions src/template_channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use color_eyre::eyre::Result;

use indexmap::IndexMap;
use poise::serenity_prelude::{CreateEmbed, CreateMessage};
use serde::{Deserialize, Serialize};

fn default_to_false() -> bool {
false
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Config {
#[serde(default)]
pub components: Vec<Component>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum Component {
Embed(EmbedComponent),
Rules(RulesComponent),
Links(LinksComponent),
Text(TextComponent),
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EmbedComponent {
#[serde(default)]
embeds: Vec<EmbedComponentEmbed>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EmbedComponentEmbed {
title: Option<String>,
description: Option<String>,
color: Option<u64>,
fields: Option<Vec<EmbedComponentEmbedField>>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EmbedComponentEmbedField {
name: String,
value: String,
#[serde(default = "default_to_false")]
inline: bool,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct RulesComponent {
#[serde(default)]
rules: IndexMap<String, String>,
#[serde(default)]
colors: Vec<u64>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct LinksComponent {
title: String,
color: Option<u64>,
links: IndexMap<String, String>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct TextComponent {
text: String,
}

impl Config {
pub fn parse(source: &str) -> Result<Self> {
Ok(toml::from_str(source)?)
}
}

impl Config {
pub fn to_messages(&self) -> Vec<CreateMessage> {
self.components
.iter()
.map(|component| match component {
Component::Embed(data) => {
let mut message = CreateMessage::default();

for embed_data in &data.embeds {
let mut embed = CreateEmbed::default();

if let Some(title) = &embed_data.title {
embed = embed.title(title);
}
if let Some(description) = &embed_data.description {
embed = embed.description(description);
}
if let Some(color) = &embed_data.color {
embed = embed.color(*color);
}
if let Some(fields) = &embed_data.fields {
embed =
embed.fields(fields.iter().map(|f| (&f.name, &f.value, f.inline)));
}

message = message.embed(embed);
}

message
}

Component::Links(data) => {
let mut embed = CreateEmbed::default().title(&data.title).description(
data.links
.iter()
.map(|(title, href)| format!("» [{title}]({href})"))
.collect::<Vec<_>>()
.join("\n"),
);

if let Some(color) = data.color {
embed = embed.color(color);
}

CreateMessage::default().embed(embed)
}

Component::Rules(data) => {
let mut message = CreateMessage::default();

for (idx, (title, desc)) in data.rules.iter().enumerate() {
let mut embed = CreateEmbed::default()
.title(format!("{}. {}", idx + 1, title))
.description(desc);

if let Some(color) = data.colors.get(idx % data.colors.len()) {
embed = embed.color(*color);
}

message = message.add_embed(embed);
}

message
}

Component::Text(data) => CreateMessage::default().content(&data.text),
})
.collect::<Vec<_>>()
}
}

0 comments on commit 3f9a8c1

Please sign in to comment.