moderatior
msg time
bendn 2024-05-08
parent 7851397 · commit 1fee302
-rw-r--r--src/db.rs29
-rw-r--r--src/help.md4
-rw-r--r--src/main.rs104
3 files changed, 125 insertions, 12 deletions
diff --git a/src/db.rs b/src/db.rs
index 448ea6a..e105728 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -1,7 +1,7 @@
// TODO pruning?
-use std::sync::LazyLock;
-
use kv::*;
+use poise::serenity_prelude::*;
+use std::sync::LazyLock;
fn cfg() -> kv::Config {
kv::Config {
@@ -23,6 +23,31 @@ pub fn set(k: u64, v: (String, Vec<String>, u64)) {
pub fn get(k: u64) -> Option<(String, Vec<String>, u64)> {
BU.get(&k.into()).unwrap().map(|x| x.0)
}
+pub fn keys() -> impl Iterator<Item = MessageId> {
+ BU.iter()
+ .filter_map(Result::ok)
+ .filter_map(|x| x.key::<u64>().ok())
+ .map(|x| MessageId::new(x))
+}
+pub fn values() -> impl Iterator<Item = (String, Vec<String>, UserId)> {
+ BU.iter()
+ .filter_map(Result::ok)
+ .filter_map(|x| x.value::<Bincode<(String, Vec<String>, u64)>>().ok())
+ .map(|Bincode((y, z, α))| (y, z, UserId::new(α)))
+}
+pub fn iter() -> impl Iterator<Item = (MessageId, (String, Vec<String>, UserId))> {
+ BU.iter()
+ .filter_map(Result::ok)
+ .filter_map(|x| {
+ x.key::<u64>()
+ .and_then(|y| {
+ x.value::<Bincode<(String, Vec<String>, u64)>>()
+ .map(|x| (y, x))
+ })
+ .ok()
+ })
+ .map(|(x, Bincode((y, z, α)))| (MessageId::new(x), (y, z, UserId::new(α))))
+}
pub fn sz() -> f32 {
DB.size_on_disk().unwrap() as f32 / (1 << 20) as f32
}
diff --git a/src/help.md b/src/help.md
index 60305c3..1bbec94 100644
--- a/src/help.md
+++ b/src/help.md
@@ -1,3 +1,3 @@
-hi i am moderatios <:icewoll:1191513368922689589>
+hi i am moderatior <:icewoll:1191513368922689589>
-i echo the audit log for all your logging pleasures \ No newline at end of file
+i echo the audit log for all your logging pleasures
diff --git a/src/main.rs b/src/main.rs
index 31b17fc..b714ce0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
#![feature(if_let_guard, let_chains, lazy_cell)]
+#![allow(confusable_idents, mixed_script_confusables)]
use anyhow::Result;
use emoji::named::*;
use poise::{serenity_prelude::*, CreateReply};
@@ -159,9 +160,12 @@ pub fn format(log: AuditLogEntry) -> Option<String> {
format!("{ROTATE} roles of <@{t}>: {changes}")
}
Member(MemberAction::RoleUpdate) => format!("{ROTATE} roles: {changes}"),
- Member(MemberAction::BanAdd) => format!("{CANCEL} banned <@&{}>", log.target_id?),
- Member(MemberAction::BanRemove) => format!("{CANCEL} unbanned <@&{}>", log.target_id?),
- Member(MemberAction::Kick) => format!("{CANCEL} kicked <@&{}>", log.target_id?),
+ Member(MemberAction::BanAdd) => format!(
+ "{CANCEL} banned <@{}> (read messages with </spy:1237586279156416592>)",
+ log.target_id?
+ ),
+ Member(MemberAction::BanRemove) => format!("{CANCEL} unbanned <@{}>", log.target_id?),
+ Member(MemberAction::Kick) => format!("{CANCEL} kicked <@{}>", log.target_id?),
_ => return None,
}
))
@@ -250,7 +254,10 @@ async fn event_handler(c: &serenity::all::Context, e: &FullEvent) -> Result<()>
.empty_users()
.empty_roles(),
)
- .content(format!("<@{author}> {EDIT} their message https://discord.com/channels/925674713429184564/{channel_id}/{id}\n```diff{diff}```")),
+ .content(format!(
+ "<t:{}:d> <@{author}> {EDIT} their message https://discord.com/channels/925674713429184564/{channel_id}/{id}\n```diff{diff}```",
+ id.created_at().unix_timestamp()
+ )),
)
.await
.unwrap();
@@ -330,7 +337,8 @@ async fn event_handler(c: &serenity::all::Context, e: &FullEvent) -> Result<()>
if a == 1224510735959462068 { return Ok(()) }
if author.get() != a || since > 20 {
format!(
- "<@{a}> {CANCEL} deleted their own message (in <#{channel_id}>):```\n{content}\n```\n{}",
+ "<t:{}:d> <@{a}> {CANCEL} deleted their own message (in <#{channel_id}>):```\n{content}\n```\n{}",
+ deleted_message_id.created_at().unix_timestamp(),
links
.into_iter()
.reduce(|a, b| format!("{a} {b}"))
@@ -338,7 +346,8 @@ async fn event_handler(c: &serenity::all::Context, e: &FullEvent) -> Result<()>
)
} else {
format!(
- "<@{who}> {CANCEL} deleted message by <@{author}> (in <#{channel_id}>):```\n{content}\n```\n{}",
+ "<t:{}:d> <@{who}> {CANCEL} deleted message by <@{author}> (in <#{channel_id}>):```\n{content}\n```\n{}",
+ deleted_message_id.created_at().unix_timestamp(),
links
.into_iter()
.reduce(|a, b| format!("{a} {b}"))
@@ -346,7 +355,10 @@ async fn event_handler(c: &serenity::all::Context, e: &FullEvent) -> Result<()>
)
}
}
- None => format!("<@{who}> {CANCEL} deleted message by <@{author}> in <#{channel_id}> (content unavailable)"),
+ None => format!(
+ "<t:{}:d> <@{who}> {CANCEL} deleted message by <@{author}> in <#{channel_id}> (content unavailable)",
+ deleted_message_id.created_at().unix_timestamp()
+ ),
}),
)
.await
@@ -365,7 +377,7 @@ impl Bot {
std::env::var("TOKEN").unwrap_or_else(|_| read_to_string("token").expect("wher token"));
let f = poise::Framework::builder()
.options(poise::FrameworkOptions {
- commands: vec![help(), reload(), redact()],
+ commands: vec![help(), reload(), redact(), spy_context(), spy_slash()],
on_error: |e| Box::pin(on_error(e)),
event_handler: |c, e, _, _| Box::pin(event_handler(c, e)),
..Default::default()
@@ -419,6 +431,82 @@ async fn main() {
Bot::spawn().await;
}
+async fn spy_(c: Context<'_>, who: UserId) -> Result<()> {
+ let h = c.reply("please check your dm's").await?;
+ let mut n = 0u64;
+ for (x, y) in db::values()
+ .filter(|(_, _, x)| *x == who)
+ .map(|(x, y, _)| (x, y))
+ {
+ if let Err(_) = c
+ .author()
+ .dm(
+ c,
+ CreateMessage::new().content(format!(
+ "```{x}```\n{}",
+ y.into_iter()
+ .reduce(|a, b| format!("{a} {b}"))
+ .unwrap_or_default()
+ )),
+ )
+ .await
+ && n == 0
+ {
+ h.edit(
+ c,
+ CreateReply::default().content("please open your dm's (couldnt send)"),
+ )
+ .await?;
+ return Ok(());
+ };
+ n += 1;
+ }
+ h.edit(
+ c,
+ CreateReply::default().content(format!("all ({n}) sent. please check your dm's")),
+ )
+ .await?;
+ Ok(())
+}
+
+#[poise::command(guild_only, context_menu_command = "Read all messages")]
+/// Collect the messages of a user.
+/// Will be dispatched to your DM's.
+/// This command may take some time.
+pub async fn spy_context(
+ c: Context<'_>,
+ #[description = "the user to spy on"] who: User,
+) -> Result<()> {
+ let u = c.author_member().await.ok_or(anyhow::anyhow!("dang"))?;
+ if !(u.user.name == "bendn"
+ || u.roles.contains(&RoleId::new(925676016708489227))
+ || u.roles.contains(&RoleId::new(925708634896367647)))
+ {
+ poise::say_reply(c, "access denied. this incident will be reported").await?;
+ return Ok(());
+ }
+ spy_(c, who.id).await
+}
+
+#[poise::command(slash_command, rename = "spy")]
+/// Collect the messages of a user.
+/// Will be dispatched to your DM's.
+/// This command may take some time.
+pub async fn spy_slash(
+ c: Context<'_>,
+ #[description = "the user to spy on"] who: String,
+) -> Result<()> {
+ let u = c.author_member().await.ok_or(anyhow::anyhow!("dang"))?;
+ if !(u.user.name == "bendn"
+ || u.roles.contains(&RoleId::new(925676016708489227))
+ || u.roles.contains(&RoleId::new(925708634896367647)))
+ {
+ poise::say_reply(c, "access denied. this incident will be reported").await?;
+ return Ok(());
+ }
+ spy_(c, UserId::new(who.parse()?)).await
+}
+
#[poise::command(slash_command)]
pub async fn reload(c: Context<'_>) -> Result<()> {
if c.author().id != OWNER {