Development (#103)

dependabot/npm_and_yarn/word-wrap-1.2.4
Samuel Shuert 2 years ago committed by GitHub
commit 74321abf33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

3
.gitignore vendored

@ -147,3 +147,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# nix
result/

@ -10,11 +10,11 @@
"sops-nix": "sops-nix"
},
"locked": {
"lastModified": 1686252801,
"narHash": "sha256-ASguQr5onfE7HzawjAvhck2y7NDZ3bdhqFC8O3/XrXU=",
"lastModified": 1687544653,
"narHash": "sha256-mZV1qMUBfZHfucHWEMvyuNZHxggaGEDafMDmMq4aQJQ=",
"ref": "refs/heads/production",
"rev": "7f3559f9a56d28b8f5352040f18305082a6dacc0",
"revCount": 53,
"rev": "18e02e77cc828ec39fb6667d54d02d176e352c1a",
"revCount": 70,
"type": "git",
"url": "ssh://git@github.com/clicksminuteper/nixfiles"
},
@ -51,11 +51,11 @@
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1686642933,
"narHash": "sha256-5Z4uitx27QtZIrG9sOSJlZRNcVQn/TI36nj7o70n5Dw=",
"lastModified": 1687378457,
"narHash": "sha256-EkRX4S60BGFlQuEYJegk/9aCODPdMV6XLI+2C2HTBEI=",
"owner": "cachix",
"repo": "devenv",
"rev": "f5278b5d56e39f86a299a2e1889906933a26f762",
"rev": "c6ac4dbf501edafe0d6860e821b9e9fff0828921",
"type": "github"
},
"original": {
@ -134,11 +134,11 @@
"systems": "systems_2"
},
"locked": {
"lastModified": 1685518550,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"lastModified": 1687171271,
"narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c",
"type": "github"
},
"original": {
@ -152,11 +152,11 @@
"systems": "systems_3"
},
"locked": {
"lastModified": 1685518550,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"lastModified": 1687171271,
"narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c",
"type": "github"
},
"original": {

@ -1 +0,0 @@
/nix/store/fmvzckvxnqrfbgv0mvzghgi87602pgcm-nucleus-1.1.0

@ -61,7 +61,7 @@ export default {
stats: {},
logging: {
logs: {
enabled: false,
enabled: true,
channel: null,
toLog: "3fffff"
},

@ -46,6 +46,61 @@ async function interactionCreate(interaction: Interaction) {
: false;
return await modifySuggestion(interaction, value);
}
if (interaction.customId === "log:message.edit") {
await interaction.channel?.messages.fetch({ message: interaction.message.id, force: true });
const attachment = interaction.message.embeds[0]?.image ?? interaction.message.attachments.first();
if (!attachment) return;
const attachmentData = await (await fetch(attachment.url)).text();
const decoded = atob(attachmentData);
const json = (
JSON.parse(decoded) as { data: { count: number; value: string; added?: boolean; removed?: boolean }[] }
).data;
// "Before" is everything where added is false
// "After" is everything where removed is false
const before: string = json
.filter((d) => !d.added)
.map((d) => d.value)
.join("");
const after: string = json
.filter((d) => !d.removed)
.map((d) => d.value)
.join("");
const { renderDateFooter } = client.logger;
await interaction.reply({
embeds: [
new EmojiEmbed()
.setTitle("Before")
.setDescription(before)
.setStatus("Danger")
.setEmoji("ICONS.OPP.ADD"),
new EmojiEmbed()
.setTitle("After")
.setDescription(after)
.setStatus("Success")
.setEmoji("ICONS.ADD")
.setFooter({ text: `Edited at ${renderDateFooter(interaction.message.createdTimestamp!)}` }) // Created timestamp of the log is when the edit was made
],
ephemeral: true
});
} else if (interaction.customId === "log:message.delete") {
await interaction.channel?.messages.fetch({ message: interaction.message.id, force: true });
const attachment = interaction.message.embeds[0]?.image ?? interaction.message.attachments.first();
if (!attachment) return;
const attachmentData = await (await fetch(attachment.url)).text();
const decoded = atob(attachmentData);
const json = JSON.parse(decoded) as { data: string };
await interaction.reply({
embeds: [
new EmojiEmbed()
.setTitle("Message")
.setDescription(json.data)
.setStatus("Danger")
.setEmoji("MESSAGE.DELETE")
.setFooter({ text: `Deleted at ${client.logger.renderDateFooter(Date.now())}` })
],
ephemeral: true
});
}
switch (interaction.customId) {
case "rolemenu": {
return await roleMenu(interaction);

@ -1,5 +1,6 @@
import type { NucleusClient } from "../utils/client.js";
import Discord, { AuditLogEvent, GuildAuditLogsEntry, Message, User } from "discord.js";
import Discord, { AuditLogEvent, ButtonStyle, GuildAuditLogsEntry, Message, User } from "discord.js";
import { imageDataEasterEgg } from "../utils/defaults.js";
export const event = "messageDelete";
@ -33,6 +34,7 @@ export async function callback(client: NucleusClient, message: Message) {
if (config) {
attachmentJump = ` [[View attachments]](${config})`;
}
const imageData = JSON.stringify({ data: message.content, extra: imageDataEasterEgg }, null, 2);
const data = {
meta: {
type: "messageDelete",
@ -40,7 +42,9 @@ export async function callback(client: NucleusClient, message: Message) {
calculateType: "messageDelete",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
timestamp: Date.now(),
imageData: imageData,
buttons: [{ buttonText: "View text", buttonId: "log:message.delete", buttonStyle: ButtonStyle.Secondary }]
},
separate: {
start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"

@ -1,7 +1,9 @@
import type { NucleusClient } from "../utils/client.js";
import { AttachmentBuilder, Message, MessageReference } from "discord.js";
import { Message, MessageReference, ButtonStyle } from "discord.js";
import type Discord from "discord.js";
import * as diff from "diff";
import addPlural from "../utils/plurals.js";
import { imageDataEasterEgg } from "../utils/defaults.js";
export const event = "messageUpdate";
@ -63,47 +65,15 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe
return;
}
const differences = diff.diffChars(oldContent, newContent);
const green = "\x1B[36m";
const red = "\x1B[41m";
const skipped = "\x1B[40;33m";
const reset = "\x1B[0m";
const bold = "\x1B[1m";
// console.log(differences);
// let contentAdd = "";
// let contentRemove = "";
// if (differences.map((d) => (d.added || d.removed ? 1 : 0)).filter((f) => f === 1).length > 0) {
// const cutoff = 20;
// differences.forEach((part) => {
// if (!part.added && !part.removed && part.value.length > cutoff) {
// contentAdd +=
// reset +
// part.value.slice(0, cutoff / 2) +
// skipped +
// `(${part.value.length - cutoff} more)` +
// reset +
// part.value.slice(-(cutoff / 2));
// contentRemove +=
// reset +
// part.value.slice(0, cutoff / 2) +
// skipped +
// `(${part.value.length - cutoff} more)` +
// reset +
// part.value.slice(-(cutoff / 2));
// } else {
// if (part.added || part.removed) {
// part.value = part.value.replaceAll(" ", "▁");
// }
// if (part.added) {
// contentAdd += green + part.value + reset;
// } else if (part.removed) {
// contentRemove += red + part.value + reset;
// } else {
// contentAdd += part.value;
// contentRemove += part.value;
// }
// }
// });
const key = `\n\n${bold}Key:${reset} ${green}Added${reset} | ${red}Removed${reset} | ${skipped}Skipped${reset}`;
const charsAdded = differences
.filter((d) => d.added)
.map((d) => d.count)
.reduce((a, b) => a! + b!, 0)!;
const charsRemoved = differences
.filter((d) => d.removed)
.map((d) => d.count)
.reduce((a, b) => a! + b!, 0)!;
const imageData = JSON.stringify({ data: differences, extra: imageDataEasterEgg }, null, 2);
const data = {
meta: {
type: "messageUpdate",
@ -112,16 +82,11 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe
color: NucleusColors.yellow,
emoji: "MESSAGE.EDIT",
timestamp: newMessage.editedTimestamp,
files: [
new AttachmentBuilder(Buffer.from(JSON.stringify(differences), "base64"), {
name: "diff.json",
description: "A JSON file containing the differences between the two messages."
})
],
showDetails: true
buttons: [{ buttonText: "View Changes", buttonStyle: ButtonStyle.Secondary, buttonId: `log:message.edit` }],
imageData: imageData
},
separate: {
start: `To read the full log press the button below.\n\`\`\`ansi\n${key}\`\`\``,
start: `${addPlural(charsAdded, "character")} added, ${addPlural(charsRemoved, "character")} removed`,
end: `[[Jump to message]](${newMessage.url})`
},
list: {

@ -471,8 +471,12 @@ export class Transcript {
if (child.type === ComponentType.Button) {
obj.style = child.style;
obj.label = child.label ?? "";
} else if (child.type > 2) {
// FIXME: Can we write this more clearly to make it obvious what we mean by 2 here?
} else if (
child.type === ComponentType.StringSelect ||
child.type === ComponentType.UserSelect ||
child.type === ComponentType.ChannelSelect ||
child.type === ComponentType.RoleSelect
) {
obj.placeholder = child.placeholder ?? "";
}
return obj;

@ -43,3 +43,10 @@ class Embed {
export { Embed };
export const unknownServerIcon = "";
export const imageDataEasterEgg =
"The image in this embed contains data about the below log.\n" +
"It isn't designed to be read by humans, but you can decode it with any base64 decoder, and then read it as JSON.\n" +
"We use base 64 to get around people using virus tests and the file being blocked, and an image to have the embed hidden (files can't be suppressed)\n" +
"If you've got to this point and are reading this hidden message, you should come and work with us " +
"at https://discord.gg/w35pXdrxKW (Internal development server) and let us know how you got here!";

@ -4,7 +4,6 @@ import { promisify } from "util";
import generateKeyValueList from "./generateKeyValueList.js";
import client from "./client.js";
import { DiscordAPIError } from "discord.js";
import { Stream } from "node:stream";
import EmojiEmbed from "./generateEmojiEmbed.js";
const wait = promisify(setTimeout);
@ -17,15 +16,8 @@ export interface LoggerOptions {
color: number;
emoji: string;
timestamp: number;
files?: (
| Discord.BufferResolvable
| Stream
| Discord.JSONEncodable<Discord.APIAttachment>
| Discord.Attachment
| Discord.AttachmentBuilder
| Discord.AttachmentPayload
)[];
showDetails?: boolean;
buttons?: { buttonText: string; buttonId: string; buttonStyle: Discord.ButtonStyle }[];
imageData?: string;
};
list: Record<string | symbol | number, unknown>;
hidden: {
@ -47,6 +39,13 @@ async function isLogging(guild: string, type: string): Promise<boolean> {
return true;
}
const NucleusColors = {
red: 0xf27878,
yellow: 0xf2d478,
green: 0x68d49e,
blue: 0x72aef5
};
export const Logger = {
renderUser(user: Discord.User | string) {
if (typeof user === "string") user = client.users.cache.get(user)!;
@ -62,6 +61,13 @@ export const Logger = {
t = Math.floor((t /= 1000));
return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`;
},
renderDateFooter(t: number) {
if (isNaN(t)) return "Unknown";
const date = new Date(t);
return `${date.getUTCFullYear()}-${
date.getUTCMonth() + 1
}-${date.getUTCDate()} at ${date.getUTCHours()}:${date.getUTCMinutes()}:${date.getUTCSeconds()} UTC`;
},
renderNumberDelta(num1: number, num2: number) {
const delta = num2 - num1;
return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`;
@ -86,11 +92,7 @@ export const Logger = {
renderEmoji(emoji: Discord.GuildEmoji) {
return `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`;
},
NucleusColors: {
red: 0xf27878,
yellow: 0xf2d478,
green: 0x68d49e
},
NucleusColors,
async getAuditLog(
guild: Discord.Guild,
event: Discord.GuildAuditLogsResolvable,
@ -106,6 +108,7 @@ export const Logger = {
throw e;
}
},
async log(log: LoggerOptions): Promise<void> {
if (!(await isLogging(log.hidden.guild, log.meta.calculateType))) return;
const config = await client.database.guilds.read(log.hidden.guild);
@ -138,17 +141,29 @@ export const Logger = {
)
.setTimestamp(log.meta.timestamp)
.setColor(log.meta.color)
.setImage(log.meta.imageData ? "attachment://extra_log_data.json.base64" : null)
];
if (log.meta.files) messageOptions.files = log.meta.files;
if (log.meta.showDetails) {
components.addComponents(
new Discord.ButtonBuilder()
.setCustomId("log:showDetails")
.setLabel("Show Details")
.setStyle(Discord.ButtonStyle.Primary)
);
if (log.meta.buttons) {
const buttons = [];
for (const button of log.meta.buttons) {
buttons.push(
new Discord.ButtonBuilder()
.setCustomId(button.buttonId)
.setLabel(button.buttonText)
.setStyle(button.buttonStyle)
);
}
components.addComponents(buttons);
messageOptions.components = [components];
}
if (log.meta.imageData) {
messageOptions.files = [
{
attachment: Buffer.from(btoa(log.meta.imageData), "utf-8"), // Use base 64 to prevent virus scanning (EICAR)
name: "extra_log_data.json.base64"
}
];
}
await channel.send(messageOptions);
}
}

Loading…
Cancel
Save