1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
use crate::{colors, consts, Context};
use color_eyre::eyre::{eyre, Result};
use once_cell::sync::Lazy;
use poise::serenity_prelude as serenity;
use rand::seq::SliceRandom;
use serenity::{CreateEmbed, GuildId, Message};
use url::Url;
pub fn parse_snowflake_from_env<T, F: Fn(u64) -> T>(key: &str, f: F) -> Option<T> {
std::env::var(key).ok().and_then(|v| v.parse().map(&f).ok())
}
pub fn parse_snowflakes_from_env<T, F: Fn(u64) -> T>(key: &str, f: F) -> Option<Vec<T>> {
std::env::var(key).ok().and_then(|gs| {
gs.split(',')
.map(|g| g.parse().map(&f))
.collect::<Result<Vec<_>, _>>()
.ok()
})
}
/*
* chooses a random element from an array
*/
pub fn random_choice<const N: usize>(arr: [&str; N]) -> Result<String> {
let mut rng = rand::thread_rng();
let resp = arr
.choose(&mut rng)
.ok_or_else(|| eyre!("couldn't choose from array!"))?;
Ok((*resp).to_string())
}
// waiting for `round_char_boundary` to stabilize
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
s.len()
} else {
let lower_bound = index.saturating_sub(3);
let new_index = s.as_bytes()[lower_bound..=index]
.iter()
.rposition(|&b| (b as i8) >= -0x40); // b.is_utf8_char_boundary
// Can be made unsafe but whatever
lower_bound + new_index.unwrap()
}
}
pub fn is_guild_allowed(gid: GuildId) -> bool {
static ALLOWED_GUILDS: Lazy<Vec<GuildId>> = Lazy::new(|| {
parse_snowflakes_from_env("ALLOWED_GUILDS", GuildId)
.unwrap_or_else(|| vec![consts::TEAWIE_GUILD, GuildId(1091969030694375444)])
});
ALLOWED_GUILDS.contains(&gid)
}
pub async fn send_url_as_embed(ctx: Context<'_>, url: String) -> Result<()> {
let parsed = Url::parse(&url)?;
let title = parsed
.path_segments()
.unwrap()
.last()
.unwrap_or("image")
.replace("%20", " ");
ctx.send(|c| {
c.embed(|e| {
e.title(title)
.image(&url)
.url(url)
.color(colors::Colors::Blue)
})
})
.await?;
Ok(())
}
pub async fn resolve_message_to_embed(ctx: &serenity::Context, msg: &Message) -> CreateEmbed {
let truncation_point = floor_char_boundary(&msg.content, 700);
let truncated_content = if msg.content.len() <= truncation_point {
msg.content.to_string()
} else {
format!("{}...", &msg.content[..truncation_point])
};
let color = msg
.member(ctx)
.await
.ok()
.and_then(|m| m.highest_role_info(&ctx.cache))
.and_then(|(role, _)| role.to_role_cached(&ctx.cache))
.map(|role| role.colour);
let attached_image = msg
.attachments
.iter()
.filter(|a| {
a.content_type
.as_ref()
.filter(|ct| ct.contains("image/"))
.is_some()
})
.map(|a| &a.url)
.next();
let attachments_len = msg.attachments.len();
let mut embed = msg
.embeds
.first()
.map(|embed| CreateEmbed::from(embed.clone()))
.unwrap_or_default();
embed.author(|author| author.name(&msg.author.name).icon_url(&msg.author.face()));
if let Some(color) = color {
embed.color(color);
}
if let Some(attachment) = attached_image {
embed.image(attachment);
}
if attachments_len > 1 {
embed.footer(|footer| {
// yes it will say '1 attachments' no i do not care
footer.text(format!("{} attachments", attachments_len))
});
}
embed.description(truncated_content);
embed
}
|