initial rewrite in rust & moderation commands
Signed-off-by: seth <getchoo@tuta.io>
This commit is contained in:
parent
b17e357b75
commit
45403e9d9b
53 changed files with 3297 additions and 2820 deletions
6
src/utils/macros.rs
Normal file
6
src/utils/macros.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
#[macro_export]
|
||||
macro_rules! required_var {
|
||||
($name: expr) => {
|
||||
std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))?
|
||||
};
|
||||
}
|
110
src/utils/mod.rs
Normal file
110
src/utils/mod.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use crate::Context;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use poise::serenity_prelude as serenity;
|
||||
use rand::seq::SliceRandom;
|
||||
use serenity::{CreateEmbed, Message};
|
||||
use url::Url;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
/*
|
||||
* 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 random object from array:\n{arr:#?}!"))?;
|
||||
|
||||
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 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)))
|
||||
.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
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { Message } from 'discord.js';
|
||||
|
||||
interface PkMessage {
|
||||
sender: string;
|
||||
}
|
||||
|
||||
export const pkDelay = 1000;
|
||||
|
||||
export async function fetchPluralKitMessage(message: Message) {
|
||||
const response = await fetch(
|
||||
`https://api.pluralkit.me/v2/messages/${message.id}`
|
||||
);
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
return (await response.json()) as PkMessage;
|
||||
}
|
||||
|
||||
export async function isMessageProxied(message: Message) {
|
||||
await new Promise((resolve) => setTimeout(resolve, pkDelay));
|
||||
return (await fetchPluralKitMessage(message)) !== null;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
interface MetaPackage {
|
||||
formatVersion: number;
|
||||
name: string;
|
||||
recommended: string[];
|
||||
uid: string;
|
||||
}
|
||||
|
||||
interface SimplifiedGHReleases {
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
// TODO: caching
|
||||
export async function getLatestMinecraftVersion(): Promise<string> {
|
||||
const f = await fetch(
|
||||
'https://meta.prismlauncher.org/v1/net.minecraft/package.json'
|
||||
);
|
||||
|
||||
const minecraft = (await f.json()) as MetaPackage;
|
||||
return minecraft.recommended[0];
|
||||
}
|
||||
|
||||
// TODO: caching
|
||||
export async function getLatestPrismLauncherVersion(): Promise<string> {
|
||||
const f = await fetch(
|
||||
'https://api.github.com/repos/PrismLauncher/PrismLauncher/releases'
|
||||
);
|
||||
const versions = (await f.json()) as SimplifiedGHReleases[];
|
||||
|
||||
return versions[0].tag_name;
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import {
|
||||
Colors,
|
||||
EmbedBuilder,
|
||||
type Message,
|
||||
ThreadChannel,
|
||||
ReactionCollector,
|
||||
} from 'discord.js';
|
||||
|
||||
function findFirstImage(message: Message): string | undefined {
|
||||
const result = message.attachments.find((attach) => {
|
||||
return attach.contentType?.startsWith('image/');
|
||||
});
|
||||
|
||||
if (result === undefined) {
|
||||
return undefined;
|
||||
} else {
|
||||
return result.url;
|
||||
}
|
||||
}
|
||||
|
||||
export async function expandDiscordLink(message: Message): Promise<void> {
|
||||
if (message.author.bot && !message.webhookId) return;
|
||||
|
||||
const re =
|
||||
/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?<serverId>\d+)\/(?<channelId>\d+)\/(?<messageId>\d+)/g;
|
||||
|
||||
const results = message.content.matchAll(re);
|
||||
const resultEmbeds: EmbedBuilder[] = [];
|
||||
|
||||
for (const r of results) {
|
||||
if (resultEmbeds.length >= 3) break; // only process three previews
|
||||
|
||||
if (r.groups == undefined || r.groups.serverId != message.guildId) continue; // do not let the bot leak messages from one server to another
|
||||
|
||||
try {
|
||||
const channel = await message.guild?.channels.fetch(r.groups.channelId);
|
||||
|
||||
if (!channel || !channel.isTextBased()) continue;
|
||||
|
||||
if (channel instanceof ThreadChannel) {
|
||||
if (
|
||||
!channel.parent?.members?.some((user) => user.id == message.author.id)
|
||||
)
|
||||
continue; // do not reveal a message to a user who can't see it
|
||||
} else {
|
||||
if (!channel.members?.some((user) => user.id == message.author.id))
|
||||
continue; // do not reveal a message to a user who can't see it
|
||||
}
|
||||
|
||||
const originalMessage = await channel.messages.fetch(r.groups.messageId);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: originalMessage.author.tag,
|
||||
iconURL: originalMessage.author.displayAvatarURL(),
|
||||
})
|
||||
.setColor(Colors.Aqua)
|
||||
.setTimestamp(originalMessage.createdTimestamp)
|
||||
.setFooter({ text: `#${originalMessage.channel.name}` });
|
||||
|
||||
embed.setDescription(
|
||||
(originalMessage.content ? originalMessage.content + '\n\n' : '') +
|
||||
`[Jump to original message](${originalMessage.url})`
|
||||
);
|
||||
|
||||
if (originalMessage.attachments.size > 0) {
|
||||
embed.addFields({
|
||||
name: 'Attachments',
|
||||
value: originalMessage.attachments
|
||||
.map((att) => `[${att.name}](${att.url})`)
|
||||
.join('\n'),
|
||||
});
|
||||
|
||||
const firstImage = findFirstImage(originalMessage);
|
||||
if (firstImage) {
|
||||
embed.setImage(firstImage);
|
||||
}
|
||||
}
|
||||
|
||||
resultEmbeds.push(embed);
|
||||
} catch (ignored) {
|
||||
/* */
|
||||
}
|
||||
}
|
||||
|
||||
if (resultEmbeds.length > 0) {
|
||||
const reply = await message.reply({
|
||||
embeds: resultEmbeds,
|
||||
allowedMentions: { repliedUser: false },
|
||||
});
|
||||
|
||||
const collector = new ReactionCollector(reply, {
|
||||
filter: (reaction) => {
|
||||
return reaction.emoji.name === '❌';
|
||||
},
|
||||
time: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
collector.on('collect', async (_, user) => {
|
||||
if (user === message.author) {
|
||||
await reply.delete();
|
||||
collector.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue