discord-egress/src/command.rs
Oliver Atkinson 79638ed324 add tracing
2024-07-25 10:56:45 -06:00

220 lines
7.6 KiB
Rust

use std::{collections::HashMap, fmt::Display, sync::Arc};
use crate::Context;
use anyhow::Error;
use poise::serenity_prelude::{Cache, CacheHttp, ChannelId, ChannelType, GetMessages, GuildChannel, Http, Message};
use tracing::error;
struct Server {
channels: Vec<Channel>,
orphanage: Vec<GuildChannel>,
needs_clean: bool,
}
struct Channel {
this: GuildChannel,
children: Vec<Channel>,
messages: Vec<Message>,
}
impl Channel {
fn new(this: GuildChannel) -> Self {
Self {
this,
// Empty vecs don't allocate until a push
children: Vec::new(),
messages: Vec::new(),
}
}
}
impl Display for Server {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn print(f: &mut std::fmt::Formatter<'_>, tab: usize, channel: &Vec<Channel>) {
for i in channel {
for _ in 0..tab { let _ = write!(f, "\t"); }
let _ = writeln!(f, "{} {}", prefix(i.this.kind),i.this.name);
print(f, tab+1, &i.children);
}
}
fn prefix(kind: ChannelType) -> &'static str {
match kind {
poise::serenity_prelude::ChannelType::Text => "💬",
poise::serenity_prelude::ChannelType::Private => "🕵️",
poise::serenity_prelude::ChannelType::Voice => "🔊",
poise::serenity_prelude::ChannelType::GroupDm => "👨‍👦‍👦",
poise::serenity_prelude::ChannelType::Category => "📁",
poise::serenity_prelude::ChannelType::News => "📢",
poise::serenity_prelude::ChannelType::NewsThread => "📢🧵",
poise::serenity_prelude::ChannelType::PublicThread => "🧵",
poise::serenity_prelude::ChannelType::PrivateThread => "🕵️🧵",
poise::serenity_prelude::ChannelType::Stage => "🎙️",
poise::serenity_prelude::ChannelType::Directory => "📒",
poise::serenity_prelude::ChannelType::Forum => "🗣️",
_ => "?",
}
}
print(f, 0, &self.channels);
if self.needs_clean {
let _ = writeln!(f, "Orphans: (please clean() before displaying...)");
for i in &self.orphanage {
let _ = write!(f, "{} {},", prefix(i.kind),i.name);
}
}
write!(f, "")
}
}
impl Server {
fn new() -> Self {
Self {
channels: Vec::new(),
orphanage: Vec::new(),
needs_clean: false
}
}
// TODO this might be broken
fn search_by_id<'a>(target: &'a mut Vec<Channel>, find: &ChannelId) -> Option<&'a mut Channel> {
for child in target {
if child.this.id == *find {
return Some(child);
}
match Self::search_by_id(&mut child.children, find) {
Some(x) => return Some(x),
None => {},
}
}
None
}
fn add(&mut self, insert: GuildChannel) {
// make sure the new item wants a parent
if let Some(parent_id) = &insert.parent_id {
// find the parent (needs to go thru all nodes)
match Self::search_by_id(&mut self.channels, &parent_id) {
Some(parent_node) => {
parent_node.children.push(Channel::new(insert));
},
None => {
// couldn't find parent, store somewhere else until it's parent is added...
self.orphanage.push(insert);
self.needs_clean = true;
},
}
} else {
self.channels.push(Channel::new(insert));
}
}
/// Cleans out the orphan channels, finding them parents. You'll want to use this before displaying anything.
fn clean(&mut self) {
if !self.needs_clean {return;}
// Look thru the orphanage and try to find parents
for orphan in &self.orphanage {
if let Some(parent_id) = orphan.parent_id {
if let Some(found) = Self::search_by_id(&mut self.channels, &parent_id) {
found.children.push(Channel::new(orphan.clone()));
} else {
panic!("⚠️ Couldn't find parent for orphan!");
}
} else {
panic!("⚠️ All orphans should want a parent node!");
}
}
self.orphanage.clear();
self.needs_clean = false;
}
/// Scrapes messages for all the channels in `self`.
async fn scrape_all(&mut self) {
let settings = GetMessages::default().limit(5);
let cache: (&Arc<Cache>, &Http) = (&Arc::new(Cache::new()), &Http::new(&crate::ENV.token));
walk_channels(&mut self.channels, cache, settings).await;
// recursive walk thru the channels
async fn walk_channels(all: &mut Vec<Channel>, cache: impl CacheHttp + Clone, settings: GetMessages) {
for channel in all {
// get the messages
match Box::pin(channel.this.messages(cache.clone(), settings)).await {
Ok(mesgs) => {
// store messages in our server object
channel.messages = mesgs;
if channel.messages.is_empty() {
error!("{} was empty - (Or incorrect permissions)", channel.this.name);
}
},
Err(e) => {
error!("{}", e);
},
}
// Clone *should* be cheap - it's Arc under the hood
Box::pin(walk_channels(&mut channel.children, cache.clone(), settings)).await;
}
}
}
/// Walk thru all the channels and count the saved messages. Will only give relevant data if
/// done after `scrape_all()`.
fn message_count(&self) -> usize {
fn walk(this: &Vec<Channel>) -> usize {
let mut total = 0;
for channel in this {
total += walk(&channel.children);
};
total
}
walk(&self.channels)
}
}
#[poise::command(slash_command, rename = "scrape_all", guild_only)]
pub async fn scrape_all(ctx: Context<'_>) -> Result<(), Error> {
let guild = ctx.guild_id().unwrap().to_partial_guild(ctx.serenity_context()).await.unwrap();
if let Ok(map) = guild.channels(ctx.http()).await {
let mut server = index(map).await;
server.scrape_all().await;
let _ = ctx.reply(&format!("Scraped {} messages", server.message_count())).await;
}
Ok(())
}
/// Get server's topology (and runs clean)
async fn index(map: HashMap<ChannelId, GuildChannel>) -> Server {
let mut server = Server::new();
// iterate thru all channels
map.into_iter().for_each(|(_id, current)| {
// println!("{} {} {:?}", current.name, current.id, current.parent_id);
server.add(current);
// TODO Take note of position
// TODO Take node of vc user limit
});
server.clean();
server
}
// NOTE!!! Make sure these names in quotes are lowercase!
#[poise::command(slash_command, rename = "index", guild_only)]
pub async fn index_cmd(ctx: Context<'_>) -> Result<(), Error> {
let guild = ctx.guild_id().unwrap().to_partial_guild(ctx.serenity_context()).await.unwrap();
match guild.channels(ctx.http()).await {
Ok(ok) => {
let server = index(ok).await;
let _ = ctx.reply(server.to_string()).await;
},
Err(_) => todo!(),
}
Ok(())
}