initial rewrite in rust & moderation commands

Signed-off-by: seth <getchoo@tuta.io>
This commit is contained in:
seth 2023-12-03 04:11:57 -05:00
parent b17e357b75
commit 45403e9d9b
No known key found for this signature in database
GPG key ID: D31BD0D494BBEE86
53 changed files with 3297 additions and 2820 deletions

View file

@ -0,0 +1,12 @@
use crate::api::dadjoke;
use crate::Context;
use color_eyre::eyre::Result;
#[poise::command(slash_command, prefix_command)]
pub async fn joke(ctx: Context<'_>) -> Result<()> {
let joke = dadjoke::get_joke().await?;
ctx.reply(joke).await?;
Ok(())
}

View file

@ -0,0 +1,25 @@
use crate::{consts, Context};
use color_eyre::eyre::{eyre, Result};
#[poise::command(slash_command, prefix_command)]
pub async fn members(ctx: Context<'_>) -> Result<()> {
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?;
let count = guild.member_count;
let online = if let Some(count) = guild.approximate_presence_count {
count.to_string()
} else {
"Undefined".to_string()
};
ctx.send(|m| {
m.embed(|e| {
e.title(format!("{count} total members!"))
.description(format!("{online} online members"))
.color(consts::COLORS["blue"])
})
})
.await?;
Ok(())
}

View file

@ -0,0 +1,13 @@
mod joke;
mod members;
mod modrinth;
mod rory;
mod say;
mod stars;
pub use joke::joke;
pub use members::members;
pub use modrinth::modrinth;
pub use rory::rory;
pub use say::say;
pub use stars::stars;

View file

@ -0,0 +1,8 @@
use crate::Context;
use color_eyre::eyre::Result;
#[poise::command(slash_command, prefix_command)]
pub async fn modrinth(ctx: Context<'_>) -> Result<()> {
todo!()
}

View file

@ -0,0 +1,21 @@
use crate::api::rory::get_rory;
use crate::Context;
use color_eyre::eyre::Result;
#[poise::command(slash_command, prefix_command)]
pub async fn rory(ctx: Context<'_>, id: Option<u64>) -> Result<()> {
let resp = get_rory(id).await?;
ctx.send(|m| {
m.embed(|e| {
e.title("Rory :3")
.url(&resp.url)
.image(resp.url)
.footer(|f| f.text(format!("ID {}", resp.id)))
})
})
.await?;
Ok(())
}

View file

@ -0,0 +1,43 @@
use crate::Context;
use color_eyre::eyre::{eyre, Result};
#[poise::command(slash_command, prefix_command, ephemeral)]
pub async fn say(ctx: Context<'_>, content: String) -> Result<()> {
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?;
let channel = ctx
.guild_channel()
.await
.ok_or_else(|| eyre!("Couldn't get channel!"))?;
channel.say(ctx, &content).await?;
ctx.say("I said what you said!").await?;
if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id {
let log_channel = guild
.channels
.iter()
.find(|c| c.0 == &channel_id)
.ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?;
log_channel
.1
.clone()
.guild()
.ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))?
.send_message(ctx, |m| {
m.embed(|e| {
e.title("Say command used!")
.description(content)
.author(|a| {
a.name(ctx.author().tag()).icon_url(
ctx.author().avatar_url().unwrap_or("undefined".to_string()),
)
})
})
})
.await?;
}
Ok(())
}

View file

@ -0,0 +1,30 @@
use crate::{consts::COLORS, Context};
use color_eyre::eyre::{Context as _, Result};
#[poise::command(slash_command, prefix_command)]
pub async fn stars(ctx: Context<'_>) -> Result<()> {
let prismlauncher = ctx
.data()
.octocrab
.repos("PrismLauncher", "PrismLauncher")
.get()
.await
.wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?;
let count = if let Some(count) = prismlauncher.stargazers_count {
count.to_string()
} else {
"undefined".to_string()
};
ctx.send(|m| {
m.embed(|e| {
e.title(format!("{count} total stars!"))
.color(COLORS["yellow"])
})
})
.await?;
Ok(())
}

View file

@ -1,11 +0,0 @@
import type { CacheType, ChatInputCommandInteraction } from 'discord.js';
export const jokeCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
await i.deferReply();
const joke = await fetch('https://icanhazdadjoke.com', {
headers: { Accept: 'text/plain' },
}).then((r) => r.text());
await i.editReply(joke);
};

View file

@ -1,24 +0,0 @@
import type { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { COLORS } from '../constants';
export const membersCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
await i.deferReply();
const memes = await i.guild?.members.fetch().then((r) => r.toJSON());
if (!memes) return;
await i.editReply({
embeds: [
{
title: `${memes.length} total members!`,
description: `${
memes.filter((m) => m.presence?.status !== 'offline').length
} online members`,
color: COLORS.blue,
},
],
});
};

20
src/commands/mod.rs Normal file
View file

@ -0,0 +1,20 @@
use crate::Data;
use color_eyre::eyre::Report;
use poise::Command;
mod general;
mod moderation;
pub fn to_global_commands() -> Vec<Command<Data, Report>> {
vec![
general::joke(),
general::members(),
general::modrinth(),
general::rory(),
general::say(),
general::stars(),
moderation::ban_user(),
moderation::kick_user(),
]
}

View file

@ -0,0 +1,80 @@
use crate::{consts::COLORS, Context};
use color_eyre::eyre::{eyre, Result};
use poise::serenity_prelude::{CreateEmbed, User};
fn create_moderation_embed(
title: String,
user: &User,
delete_messages_days: Option<u8>,
reason: String,
) -> impl FnOnce(&mut CreateEmbed) -> &mut CreateEmbed {
let fields = [
("User", format!("{} ({})", user.name, user.id), false),
("Reason", reason, false),
(
"Deleted messages",
format!("Last {} days", delete_messages_days.unwrap_or(0)),
false,
),
];
|e: &mut CreateEmbed| e.title(title).fields(fields).color(COLORS["red"])
}
// ban a user
#[poise::command(
slash_command,
prefix_command,
default_member_permissions = "BAN_MEMBERS"
)]
pub async fn ban_user(
ctx: Context<'_>,
user: User,
delete_messages_days: Option<u8>,
reason: Option<String>,
) -> Result<()> {
let days = delete_messages_days.unwrap_or(1);
let guild = ctx
.guild()
.ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?;
let reason = reason.unwrap_or("n/a".to_string());
if reason != "n/a" {
guild.ban_with_reason(ctx, &user, days, &reason).await?;
} else {
guild.ban(ctx, &user, days).await?;
}
let embed = create_moderation_embed("User banned!".to_string(), &user, Some(days), reason);
ctx.send(|m| m.embed(embed)).await?;
Ok(())
}
// kick a user
#[poise::command(
slash_command,
prefix_command,
default_member_permissions = "KICK_MEMBERS"
)]
pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option<String>) -> Result<()> {
let guild = ctx
.guild()
.ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?;
let reason = reason.unwrap_or("n/a".to_string());
if reason != "n/a" {
guild.kick_with_reason(ctx, &user, &reason).await?;
} else {
guild.kick(ctx, &user).await?;
}
let embed = create_moderation_embed("User kicked!".to_string(), &user, None, reason);
ctx.send(|m| m.embed(embed)).await?;
Ok(())
}

View file

@ -0,0 +1,3 @@
mod actions;
pub use actions::*;

View file

@ -1,124 +0,0 @@
type Side = 'required' | 'optional' | 'unsupported';
export interface ModrinthProject {
slug: string;
title: string;
description: string;
categories: string[];
client_side: Side;
server_side: Side;
project_type: 'mod' | 'modpack';
downloads: number;
icon_url: string | null;
id: string;
team: string;
}
import {
EmbedBuilder,
type CacheType,
type ChatInputCommandInteraction,
} from 'discord.js';
import { COLORS } from '../constants';
export const modrinthCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
await i.deferReply();
const { value: id } = i.options.get('id') ?? { value: null };
if (!id || typeof id !== 'string') {
await i.editReply({
embeds: [
new EmbedBuilder()
.setTitle('Error!')
.setDescription('You need to provide a valid mod ID!')
.setColor(COLORS.red),
],
});
return;
}
const res = await fetch('https://api.modrinth.com/v2/project/' + id);
if (!res.ok) {
await i.editReply({
embeds: [
new EmbedBuilder()
.setTitle('Error!')
.setDescription('Not found!')
.setColor(COLORS.red),
],
});
setTimeout(() => {
i.deleteReply();
}, 3000);
return;
}
const data = (await res.json()) as
| ModrinthProject
| { error: string; description: string };
if ('error' in data) {
console.error(data);
await i.editReply({
embeds: [
new EmbedBuilder()
.setTitle('Error!')
.setDescription(`\`${data.error}\` ${data.description}`)
.setColor(COLORS.red),
],
});
setTimeout(() => {
i.deleteReply();
}, 3000);
return;
}
await i.editReply({
embeds: [
new EmbedBuilder()
.setTitle(data.title)
.setDescription(data.description)
.setThumbnail(data.icon_url)
.setURL(`https://modrinth.com/project/${data.slug}`)
.setFields([
{
name: 'Categories',
value: data.categories.join(', '),
inline: true,
},
{
name: 'Project type',
value: data.project_type,
inline: true,
},
{
name: 'Downloads',
value: data.downloads.toString(),
inline: true,
},
{
name: 'Client',
value: data.client_side,
inline: true,
},
{
name: 'Server',
value: data.server_side,
inline: true,
},
])
.setColor(COLORS.green),
],
});
};

View file

@ -1,51 +0,0 @@
import type { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
export interface RoryResponse {
/**
* The ID of this Rory
*/
id: number;
/**
* The URL to the image of this Rory
*/
url: string;
/**
* When error :(
*/
error: string | undefined;
}
export const roryCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
await i.deferReply();
const { value: id } = i.options.get('id') ?? { value: '' };
const rory: RoryResponse = await fetch(`https://rory.cat/purr/${id}`, {
headers: { Accept: 'application/json' },
}).then((r) => r.json());
if (rory.error) {
await i.editReply({
embeds: [
new EmbedBuilder().setTitle('Error!').setDescription(rory.error),
],
});
return;
}
await i.editReply({
embeds: [
new EmbedBuilder()
.setTitle('Rory :3')
.setURL(`https://rory.cat/id/${rory.id}`)
.setImage(rory.url)
.setFooter({
text: `ID ${rory.id}`,
}),
],
});
};

View file

@ -1,37 +0,0 @@
import {
CacheType,
ChatInputCommandInteraction,
EmbedBuilder,
} from 'discord.js';
export const sayCommand = async (
interaction: ChatInputCommandInteraction<CacheType>
) => {
if (!interaction.guild || !interaction.channel) return;
const content = interaction.options.getString('content', true);
await interaction.deferReply({ ephemeral: true });
const message = await interaction.channel.send(content);
await interaction.editReply('I said what you said!');
if (process.env.SAY_LOGS_CHANNEL) {
const logsChannel = await interaction.guild.channels.fetch(
process.env.SAY_LOGS_CHANNEL
);
if (!logsChannel?.isTextBased()) return;
await logsChannel.send({
embeds: [
new EmbedBuilder()
.setTitle('Say command used')
.setDescription(content)
.setAuthor({
name: interaction.user.tag,
iconURL: interaction.user.avatarURL() ?? undefined,
})
.setURL(message.url),
],
});
}
};

View file

@ -1,23 +0,0 @@
import type { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { COLORS } from '../constants';
export const starsCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
await i.deferReply();
const count = await fetch(
'https://api.github.com/repos/PrismLauncher/PrismLauncher'
)
.then((r) => r.json() as Promise<{ stargazers_count: number }>)
.then((j) => j.stargazers_count);
await i.editReply({
embeds: [
{
title: `${count} total stars!`,
color: COLORS.yellow,
},
],
});
};

View file

@ -1,38 +0,0 @@
import {
type ChatInputCommandInteraction,
type CacheType,
EmbedBuilder,
} from 'discord.js';
import { getTags } from '../tags';
export const tagsCommand = async (
i: ChatInputCommandInteraction<CacheType>
) => {
const tags = await getTags();
const tagName = i.options.getString('name', true);
const mention = i.options.getUser('user', false);
const tag = tags.find(
(tag) => tag.name === tagName || tag.aliases?.includes(tagName)
);
if (!tag) {
await i.reply({
content: `Tag \`${tagName}\` does not exist.`,
ephemeral: true,
});
return;
}
const embed = new EmbedBuilder();
embed.setTitle(tag.title ?? tag.name);
embed.setDescription(tag.content);
if (tag.color) embed.setColor(tag.color);
if (tag.image) embed.setImage(tag.image);
if (tag.fields) embed.setFields(tag.fields);
await i.reply({
content: mention ? `<@${mention.id}> ` : undefined,
embeds: [embed],
});
};