Compare commits

..

3 commits

Author SHA1 Message Date
powermaker450 4c1698e12f Update dependencies 2025-01-17 14:17:51 -05:00
powermaker450 34118d5e47 Continue to ignore the old persistent directory 2025-01-17 14:01:39 -05:00
powermaker450 97abd1fcee First implementation of sqlite 2025-01-16 23:16:06 -05:00
12 changed files with 251 additions and 367 deletions

4
.gitignore vendored
View file

@ -1,4 +1,6 @@
.env .env
node_modules/ node_modules/
dist/
persist/ persist/
dist/
prisma/migrations/
prisma/persist/

View file

@ -13,6 +13,7 @@
"author": "powermaker450", "author": "powermaker450",
"license": "AGPLv3", "license": "AGPLv3",
"dependencies": { "dependencies": {
"@prisma/client": "^6.2.1",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"discord.js": "^14.16.3", "discord.js": "^14.16.3",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@ -21,6 +22,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prisma": "^6.2.1",
"typescript": "^5.6.3" "typescript": "^5.6.3"
} }
} }

View file

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@prisma/client':
specifier: ^6.2.1
version: 6.2.1(prisma@6.2.1)
chalk: chalk:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.2 version: 4.1.2
@ -27,6 +30,9 @@ importers:
prettier: prettier:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3 version: 3.3.3
prisma:
specifier: ^6.2.1
version: 6.2.1
typescript: typescript:
specifier: ^5.6.3 specifier: ^5.6.3
version: 5.6.3 version: 5.6.3
@ -61,6 +67,30 @@ packages:
resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==}
engines: {node: '>=16.11.0'} engines: {node: '>=16.11.0'}
'@prisma/client@6.2.1':
resolution: {integrity: sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==}
engines: {node: '>=18.18'}
peerDependencies:
prisma: '*'
peerDependenciesMeta:
prisma:
optional: true
'@prisma/debug@6.2.1':
resolution: {integrity: sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==}
'@prisma/engines-version@6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69':
resolution: {integrity: sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==}
'@prisma/engines@6.2.1':
resolution: {integrity: sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==}
'@prisma/fetch-engine@6.2.1':
resolution: {integrity: sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==}
'@prisma/get-platform@6.2.1':
resolution: {integrity: sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==}
'@sapphire/async-queue@1.5.3': '@sapphire/async-queue@1.5.3':
resolution: {integrity: sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==} resolution: {integrity: sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'} engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
@ -118,6 +148,11 @@ packages:
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
has-flag@4.0.0: has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -139,6 +174,11 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
prisma@6.2.1:
resolution: {integrity: sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==}
engines: {node: '>=18.18'}
hasBin: true
supports-color@7.2.0: supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -225,6 +265,31 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
'@prisma/client@6.2.1(prisma@6.2.1)':
optionalDependencies:
prisma: 6.2.1
'@prisma/debug@6.2.1': {}
'@prisma/engines-version@6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69': {}
'@prisma/engines@6.2.1':
dependencies:
'@prisma/debug': 6.2.1
'@prisma/engines-version': 6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69
'@prisma/fetch-engine': 6.2.1
'@prisma/get-platform': 6.2.1
'@prisma/fetch-engine@6.2.1':
dependencies:
'@prisma/debug': 6.2.1
'@prisma/engines-version': 6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69
'@prisma/get-platform': 6.2.1
'@prisma/get-platform@6.2.1':
dependencies:
'@prisma/debug': 6.2.1
'@sapphire/async-queue@1.5.3': {} '@sapphire/async-queue@1.5.3': {}
'@sapphire/shapeshift@4.0.0': '@sapphire/shapeshift@4.0.0':
@ -287,6 +352,9 @@ snapshots:
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fsevents@2.3.3:
optional: true
has-flag@4.0.0: {} has-flag@4.0.0: {}
lodash.snakecase@4.1.1: {} lodash.snakecase@4.1.1: {}
@ -299,6 +367,12 @@ snapshots:
prettier@3.3.3: {} prettier@3.3.3: {}
prisma@6.2.1:
dependencies:
'@prisma/engines': 6.2.1
optionalDependencies:
fsevents: 2.3.3
supports-color@7.2.0: supports-color@7.2.0:
dependencies: dependencies:
has-flag: 4.0.0 has-flag: 4.0.0

38
prisma/schema.prisma Normal file
View file

@ -0,0 +1,38 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Guild {
guildId String @id
confessChannel String @unique
modChannel String? @unique
confessions Confession[]
bans Ban[]
}
model Confession {
id String @id
messageId String @unique
author String
authorId String
guildId String
content String
attachment String?
guild Guild @relation(fields: [guildId], references: [guildId])
}
model Ban {
authorId String @id
guildId String
confessionId String?
reason Int
guild Guild @relation(fields: [guildId], references: [guildId])
}

View file

@ -92,7 +92,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
if (interaction.options.getSubcommand() === "ban") { if (interaction.options.getSubcommand() === "ban") {
const confessionId = interaction.options.getString("id")!; const confessionId = interaction.options.getString("id")!;
if (dt.isBannedById(guildId, confessionId)) { if (await dt.isBannedById(guildId, confessionId)) {
return interaction return interaction
.reply({ .reply({
content: "That user is already banned!", content: "That user is already banned!",
@ -101,7 +101,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
.catch(err => logger.error("A ban interaction error occured:", err)); .catch(err => logger.error("A ban interaction error occured:", err));
} }
const result = dt.addBanById(guildId, confessionId); const result = await dt.addBanById(guildId, confessionId);
return interaction return interaction
.reply({ .reply({
@ -116,7 +116,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
} else if (interaction.options.getSubcommand() === "banuser") { } else if (interaction.options.getSubcommand() === "banuser") {
const { id: userId } = interaction.options.getUser("user")!; const { id: userId } = interaction.options.getUser("user")!;
const result = dt.addBanByUser(guildId, userId); const result = await dt.addBanByUser(guildId, userId);
return interaction return interaction
.reply({ .reply({
@ -127,9 +127,9 @@ export async function execute(interaction: ChatInputCommandInteraction) {
// /confessmod list // /confessmod list
} else if (interaction.options.getSubcommand() === "list") { } else if (interaction.options.getSubcommand() === "list") {
const bannedMembers = dt.getBans(guildId); const bannedMembers = await dt.getBans(guildId);
const determineContent = () => { const determineContent = async () => {
if (!bannedMembers.length) { if (!bannedMembers.length) {
return "There are no bans."; return "There are no bans.";
} }
@ -140,11 +140,11 @@ export async function execute(interaction: ChatInputCommandInteraction) {
let idHead = "\n" + heading("Confessions:", HeadingLevel.Two); let idHead = "\n" + heading("Confessions:", HeadingLevel.Two);
let idCount = false; let idCount = false;
for (const member of bannedMembers) { for (const member of bannedMembers) {
if (member.method === BanReason.ByUser) { if (member.reason === BanReason.ByUser) {
userHead += "\n" + `<@${member.user}>`; userHead += "\n" + `<@${member.authorId}>`;
userCount = true; userCount = true;
} else if (member.method === BanReason.ById) { } else if (member.reason === BanReason.ById) {
const confession = dt.getConfession(guildId, member.confessionId!)!; const confession = (await dt.getConfession(guildId, member.confessionId!))!;
idHead += `\nConfession ${inlineCode(member.confessionId!)}: ${italic(confession.content)}`; idHead += `\nConfession ${inlineCode(member.confessionId!)}: ${italic(confession.content)}`;
idCount = true; idCount = true;
} }
@ -160,14 +160,14 @@ export async function execute(interaction: ChatInputCommandInteraction) {
return interaction return interaction
.reply({ .reply({
content: determineContent(), content: await determineContent(),
...messageOpts ...messageOpts
}) })
.catch(err => logger.error("A banlist interaction error occured:", err)); .catch(err => logger.error("A banlist interaction error occured:", err));
// /confessmod pardon <id> // /confessmod pardon <id>
} else if (interaction.options.getSubcommand() === "pardon") { } else if (interaction.options.getSubcommand() === "pardon") {
const result = dt.removeBanById( const result = await dt.removeBanById(
guildId, guildId,
interaction.options.getString("id")! interaction.options.getString("id")!
); );
@ -185,7 +185,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
} else if (interaction.options.getSubcommand() === "pardonuser") { } else if (interaction.options.getSubcommand() === "pardonuser") {
const { id: userId } = interaction.options.getUser("user")!; const { id: userId } = interaction.options.getUser("user")!;
const result = dt.removeBanByUser(guildId, userId); const result = await dt.removeBanByUser(guildId, userId);
return interaction return interaction
.reply({ .reply({

View file

@ -42,7 +42,7 @@ export async function execute(interaction: CommandInteraction) {
const { id: guildId } = interaction.guild!; const { id: guildId } = interaction.guild!;
const { displayName: username } = interaction.user; const { displayName: username } = interaction.user;
if (dt.checkSetup(guildId)) { if (await dt.checkSetup(guildId)) {
return interaction return interaction
.reply({ .reply({
content: "This guild has already been set up!", content: "This guild has already been set up!",

View file

@ -38,7 +38,7 @@ export async function deleteConfession(
const { id: guildId } = interaction.guild!; const { id: guildId } = interaction.guild!;
const { id: userId } = interaction.user; const { id: userId } = interaction.user;
const result = dt.getConfession(guildId, idVal); const result = await dt.getConfession(guildId, idVal);
// If there is a result, and the user is either an author or has manage messages // If there is a result, and the user is either an author or has manage messages
const allowedByUser = result && result.authorId === userId; const allowedByUser = result && result.authorId === userId;
const allowedByMod = const allowedByMod =
@ -48,8 +48,8 @@ export async function deleteConfession(
// If a confession is found with the given ID, check if the user is the one that posted it, and delete it if they are. // If a confession is found with the given ID, check if the user is the one that posted it, and delete it if they are.
// Otherwise, don't let the user delete anything. // Otherwise, don't let the user delete anything.
if (allowedByUser || allowedByMod) { if (allowedByUser || allowedByMod) {
const confession = dt.getConfession(guildId, idVal)!.messageId; const confession = (await dt.getConfession(guildId, idVal))!.messageId;
const channelId = dt.getGuildInfo(guildId)!.settings.confessChannel; const channelId = (await dt.getGuildInfo(guildId))!.confessChannel;
const emptyEmbed = new EmbedBuilder() const emptyEmbed = new EmbedBuilder()
.setColor(getRandomColor()) .setColor(getRandomColor())
.setTitle("Confession Deleted") .setTitle("Confession Deleted")

View file

@ -27,7 +27,7 @@ export async function submitConfession(
const { id: userId, displayName: username } = interaction.user; const { id: userId, displayName: username } = interaction.user;
// If the user is banned in this guild, don't let them post // If the user is banned in this guild, don't let them post
if (dt.isBannedByUser(guildId, userId)) { if (await dt.isBannedByUser(guildId, userId)) {
return interaction.reply({ return interaction.reply({
content: "You are banned from confessions in this server!", content: "You are banned from confessions in this server!",
...messageOpts ...messageOpts
@ -35,7 +35,7 @@ export async function submitConfession(
} }
// If no guild info is present for this guild, don't let the user post // If no guild info is present for this guild, don't let the user post
if (!dt.getGuildInfo(guildId)) { if (!(await dt.getGuildInfo(guildId))) {
return interaction.reply({ return interaction.reply({
content: content:
"The bot hasn't been set up yet! Ask the server admins to set it up.", "The bot hasn't been set up yet! Ask the server admins to set it up.",
@ -43,8 +43,7 @@ export async function submitConfession(
}); });
} }
const confessChannel = dt.getGuildInfo(guildId)!.settings.confessChannel; const { confessChannel, modChannel } = (await dt.getGuildInfo(guildId))!;
const adminChannel = dt.getGuildInfo(guildId)?.settings.modChannel;
const isAttachment = (text: string | null) => const isAttachment = (text: string | null) =>
text && (text.startsWith("http://") || text.startsWith("https://")); text && (text.startsWith("http://") || text.startsWith("https://"));
@ -125,10 +124,10 @@ export async function submitConfession(
components: [actionRow] components: [actionRow]
}); });
adminChannel && modChannel &&
(await (BotClient.channels.cache.get(adminChannel!) as TextChannel).send({ (BotClient.channels.cache.get(modChannel!) as TextChannel).send({
embeds: [adminConfessionEmbed] embeds: [adminConfessionEmbed]
})); });
const fields: readonly [Message, string, string, string, string] = [ const fields: readonly [Message, string, string, string, string] = [
message, message,
@ -142,23 +141,12 @@ export async function submitConfession(
? dt.addConfession(...fields, attachmentContent) ? dt.addConfession(...fields, attachmentContent)
: dt.addConfession(...fields); : dt.addConfession(...fields);
const confessionsLength = dt.getGuildInfo(guildId)!.confessions.length; const confessions = await dt.getConfessions(guildId);
// If there are 2 or more confessions, remove the previous confession's button components // If there are 2 or more confessions, remove the previous confession's button components
if (confessionsLength >= 2) { if (confessions.length >= 2) {
(BotClient.channels.cache.get(confessChannel) as TextChannel).messages const previousMessage = await (BotClient.channels.cache.get(confessChannel) as TextChannel).messages.fetch(confessions[confessions.length - 2].messageId);
.fetch( previousMessage.edit({ components: [] }).catch(err => logger.error("An error occured removing embeds from the previous message:", err));
dt.getGuildInfo(guildId)!.confessions[confessionsLength - 2].messageId
)
.then(message => {
message.edit({ components: [] });
})
.catch(err => {
logger.error(
"An error occured removing embeds from the previous message:",
err
);
});
} }
return interaction.reply({ return interaction.reply({

View file

@ -42,13 +42,12 @@ export async function execute(interaction: ContextMenuCommandInteraction) {
if (!dt.getConfessionById(guildId!, targetId)) { if (!dt.getConfessionById(guildId!, targetId)) {
return interaction.reply({ return interaction.reply({
content: content: "Either that confession wasn't found or you aren't allowed to remove it.",
"Either that confession wasn't found or you aren't allowed to remove it.",
...messageOpts ...messageOpts
}); });
} }
const { id: confessionId } = dt.getConfessionById(guildId!, targetId)!; const { id: confessionId } = (await dt.getConfessionById(guildId!, targetId))!;
try { try {
deleteConfession(interaction, confessionId); deleteConfession(interaction, confessionId);

View file

@ -16,7 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ChatInputCommandInteraction, Events } from "discord.js"; import {
ChatInputCommandInteraction,
Events
} from "discord.js";
import { BotClient, BOT_TOKEN, deployCommands } from "./bot"; import { BotClient, BOT_TOKEN, deployCommands } from "./bot";
import { commands } from "./commands"; import { commands } from "./commands";
import { StoreMan } from "./storeman"; import { StoreMan } from "./storeman";
@ -26,13 +29,11 @@ import { messageOpts } from "./constants";
import { submitConfession } from "./commandutils"; import { submitConfession } from "./commandutils";
import { contextCommands } from "./contextcommands"; import { contextCommands } from "./contextcommands";
export const dt = new StoreMan(StoreMan.checkFile()); export const dt = new StoreMan();
const logger = new Logger("Main"); const logger = new Logger("Main");
BotClient.once("ready", client => { BotClient.once("ready", client => {
logger.log(`We're ready! Logged in as ${client.user.tag}`); logger.log(`We're ready! Logged in as ${client.user.tag}`);
dt.sendReleaseNotes();
}); });
// Deploy the commands for a new guild // Deploy the commands for a new guild
@ -74,25 +75,7 @@ BotClient.on(Events.InteractionCreate, async interaction => {
} }
}); });
BotClient.on(Events.MessageDelete, async message => { // BotClient.on(Events.MessageDelete, async message => {});
const guildId = message.guild?.id!;
if (!dt.getGuildInfo(guildId)) {
return;
}
try {
const messageId = message.id;
const confessions = dt.getGuildInfo(guildId)?.confessions!;
for (const confession of confessions) {
if (confession.messageId === messageId) {
dt.adminDelConfession(guildId, confession.id);
}
}
} catch (err) {
logger.error("An error occured:", err);
}
});
// Submit Confession button // Submit Confession button
BotClient.on(Events.InteractionCreate, async interaction => { BotClient.on(Events.InteractionCreate, async interaction => {
@ -107,7 +90,7 @@ BotClient.on(Events.InteractionCreate, async interaction => {
if (requestSubmit) { if (requestSubmit) {
// Check if the user is banned from confessions before showing the modal // Check if the user is banned from confessions before showing the modal
dt.isBannedByUser(interaction.guild?.id!, interaction.user.id) await dt.isBannedByUser(interaction.guild?.id!, interaction.user.id)
? interaction ? interaction
.reply({ .reply({
content: "You are banned from confessions in this server!", content: "You are banned from confessions in this server!",

View file

@ -16,380 +16,179 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import fs from "fs";
import crypto from "crypto"; import crypto from "crypto";
import { import {
BanReason, BanReason,
Confession,
ConfessionBan,
GuildData,
GuildSettings GuildSettings
} from "./types"; } from "./types";
import { DATA_DIR } from "./config"; import { CommandInteraction, Message } from "discord.js";
import { import { Guild, Confession, PrismaClient, Ban } from "@prisma/client";
bold,
CommandInteraction,
heading,
Message,
TextChannel,
unorderedList
} from "discord.js";
import Logger from "../utils/Logger"; import Logger from "../utils/Logger";
import { BotClient } from "../bot";
export class StoreMan { export class StoreMan {
public static readonly fullPath: string =
(DATA_DIR ?? "./persist/") + "data.json";
private static logger = new Logger("StoreMan"); private static logger = new Logger("StoreMan");
private data: GuildData[]; private static checkResult = (result: any | null) => result ? true : false;
constructor(existingData: GuildData[] = []) { private client: PrismaClient;
this.data = existingData;
constructor() {
this.client = new PrismaClient();
} }
public static genId = () => crypto.randomBytes(2).toString("hex"); public static genId = () => crypto.randomBytes(2).toString("hex");
public static toConfession(
message: Message,
id: string,
author: string,
authorId: string,
content: string,
attachment?: string
): Confession {
return {
id: id,
messageId: message.id,
author: author,
authorId: authorId,
content: content,
attachment: attachment
};
}
public static checkFile(): GuildData[] {
let final: GuildData[];
if (fs.existsSync(StoreMan.fullPath)) {
const data = fs.readFileSync(StoreMan.fullPath);
// Read the file if it isn't empty, else set final to an empty array
final = !data.toString().trim() ? [] : JSON.parse(data.toString());
} else {
// If the directory doesn't exist, make it
!fs.existsSync(DATA_DIR ?? "./persist/") &&
fs.mkdirSync(DATA_DIR ?? "./persist/");
fs.createWriteStream(StoreMan.fullPath);
final = [];
}
return final;
}
public async saveFile(): Promise<void> {
fs.writeFile(
StoreMan.fullPath,
JSON.stringify(this.data, null, 2),
"utf8",
err => err && StoreMan.logger.error("A write error occured:", err)
);
}
// Checks if a guild is not set up // Checks if a guild is not set up
public checkSetup(guildId: string): boolean { public async checkSetup(guildId: string): Promise<boolean> {
for (const guild of this.data) { const result = await this.client.guild.findFirst({ where: { guildId } }).then(StoreMan.checkResult);
if (guild.id === guildId) {
return true;
}
}
return false; return result;
} }
// Sets up a guild and stores it in the persistent file // Sets up a guild and stores it in the persistent file
public setup(guildId: string, opts: GuildSettings): void { public async setup(guildId: string, { confessChannel, modChannel }: GuildSettings): Promise<void> {
this.data.push({ await this.client.guild.create({ data: { guildId, confessChannel, modChannel } }).then(() => StoreMan.logger.log("Guild created"));
id: guildId,
confessions: [],
settings: opts,
versionNote: "v0.1.1"
});
this.saveFile();
} }
// Clear the settings for a given guild // Clear the settings for a given guild
public clearSettings(guildId: string): void { public async clearSettings(guildId: string): Promise<void> {
this.data = this.data.filter(guild => { await this.client.confession.deleteMany({ where: { guildId } });
return guild.id !== guildId; await this.client.ban.deleteMany({ where: { guildId } });
}); await this.client.guild.delete({ where: { guildId } });
this.saveFile();
} }
public getGuildInfo(guildId: string): GuildData | null { public async getGuildInfo(guildId: string): Promise<Guild | null> {
for (const guild of this.data) { return await this.client.guild.findFirst({ where: { guildId } });
if (guild.id === guildId) {
return guild;
}
}
return null;
}
public sendReleaseNotes(): void {
for (const guild of this.data) {
if (!guild.settings.modChannel) {
return;
}
if (guild.versionNote !== "v0.1.1") {
// TODO: Manual release notes for now
const channel = BotClient.channels.cache.get(guild.settings.modChannel);
const content =
heading("🎉 Release v0.1.1\n") +
unorderedList([
"No notable changes with this release, just popping in to say hi! :)",
"You'll get updates about future releases right here in the mod channel!"
]) +
"\n\n" +
bold("Full Changelog: ") +
"https://codeberg.org/powermaker450/confoss/commits/tag/v0.1.1";
(channel as TextChannel).send(content).catch(StoreMan.logger.log);
guild.versionNote = "v0.1.1";
}
}
this.saveFile();
} }
// Attempts to add a confession. Returns true if the confession is sent, false if otherwise. // Attempts to add a confession. Returns true if the confession is sent, false if otherwise.
public addConfession( public async addConfession(
message: Message, message: Message,
id: string, id: string,
author: string, author: string,
authorId: string, authorId: string,
content: string, content: string,
attachment?: string attachment?: string
): boolean { ): Promise<boolean> {
const { id: guildId } = message.guild!; const { id: guildId } = message.guild!;
const { id: messageId } = message;
for (const guild of this.data) { const ban = await this.client.ban.findFirst({ where: { guildId, authorId } }).then(StoreMan.checkResult);
if (guild.id === guildId) {
// If the author's user ID is in the ban list, don't let them post a confession.
if (this.isBannedByUser(guildId, author)) {
return false;
}
guild.confessions.push( if (ban) {
StoreMan.toConfession( return false;
message,
id,
author,
authorId,
content,
attachment
)
);
this.saveFile();
return true;
}
} }
throw new Error( await this.client.confession.create({ data: { id, messageId, author, authorId, guildId, content, attachment } })
`No guild with id ${id} was found. Something's pretty wrong.` return true;
);
} }
public getConfession( public async getConfession(
guildId: string, guildId: string,
confessionId: string id: string
): Confession | null { ): Promise<Confession | null> {
for (const guild of this.data) { return await this.client.confession.findFirst({ where: { guildId, id } });
if (guild.id === guildId) {
for (const confession of guild.confessions) {
if (confession.id === confessionId) {
return confession;
}
}
}
}
return null;
} }
public getConfessionById( public async getConfessions(guildId: string): Promise<Confession[]> {
guildId: string, return await this.client.confession.findMany({ where: { guildId } });
messageId: string
): Confession | null {
for (const guild of this.data) {
if (guild.id === guildId) {
for (const confession of guild.confessions) {
if (confession.messageId === messageId) {
return confession;
}
}
}
}
return null;
} }
// Attempts to delete a confession. If it is sucessfully deleted, returns true, else false. public async getConfessionById(guildId: string, messageId: string): Promise<Confession | null> {
public delConfesssion( return await this.client.confession.findFirst({ where: { guildId, messageId } });
{ guild, user }: CommandInteraction,
confessionId: string
): boolean {
const guildId = guild?.id;
const userId = user.id;
for (const guild of this.data) {
if (guild.id === guildId) {
for (const confession of guild.confessions) {
if (confession.authorId === userId) {
guild.confessions = guild.confessions.filter(confession => {
return confession.id !== confessionId;
});
this.saveFile();
return true;
}
}
}
}
return false;
} }
public adminDelConfession(guildId: string, confessionId: string): void { /**
for (const guild of this.data) { * Delete a confession from the database.
if (guild.id === guildId) { *
guild.confessions = guild.confessions.filter(confession => { * @param interaction - Used to obtain the guild and authorId
return confession.id !== confessionId; * @param id - The confession ID to delete
}); *
* @returns true if the confession was sucessfully deleted, false if otherwise.
*/
public async delConfesssion(
{ guild, user: { id: authorId } }: CommandInteraction,
id: string
): Promise<boolean> {
const { id: guildId } = guild!;
const result = await this.client.confession.delete({ where: { guildId, authorId, id } }).then(StoreMan.checkResult);
this.saveFile(); return result;
} }
}
/**
* Delete a confession from the database as an admin.
*
* @param guildId - The ID of the guild to delete from
* @param id - The ID of the confession to delete
*/
public async adminDelConfession(guildId: string, id: string): Promise<void> {
await this.client.confession.delete({ where: { guildId, id } });
} }
// Check if a certain user is banned within a guild. // Check if a certain user is banned within a guild.
public isBannedByUser(guildId: string, userId: string): boolean { public async isBannedByUser(guildId: string, authorId: string): Promise<boolean> {
for (const guild of this.data) { const result = await this.client.ban.findFirst({ where: { guildId, authorId } }).then(StoreMan.checkResult);
if (guild.id === guildId) {
for (const ban of guild.settings.bans) {
if (ban.user === userId) {
return true;
}
}
}
}
return false; return result;
} }
public isBannedById(guildId: string, confessionId: string): boolean { public async isBannedById(guildId: string, confessionId: string): Promise<boolean> {
for (const guild of this.data) { const result = await this.client.ban.findFirst({ where: { guildId, confessionId } }).then(StoreMan.checkResult);
if (guild.id === guildId) {
for (const ban of guild.settings.bans) {
if (ban.confessionId === confessionId) {
return true;
}
}
}
}
return false; return result;
} }
public getBans(guildId: string): ConfessionBan[] { public async getBans(guildId: string): Promise<Ban[]> {
for (const guild of this.data) { return await this.client.ban.findMany({ where: { guildId } });
if (guild.id === guildId) {
return guild.settings.bans;
}
}
return [];
} }
// Attempts to ban a user from confessions. /**
public addBanById(guildId: string, confessionId: string): boolean { * Ban a user by confession id.
const confession = this.getConfession(guildId, confessionId); *
* @param guildId - The ID of the guild to ban from
for (const guild of this.data) { * @param confessionId - The ID of the confession to ban for
if (guild.id === guildId) { */
if (confession) { public async addBanById(guildId: string, confessionId: string): Promise<boolean> {
// Only add the user to the ban list if they aren't banned already const alreadyBanned = await this.isBannedById(guildId, confessionId);
!this.isBannedByUser(guildId, confession.authorId) &&
guild.settings.bans.push({ if (alreadyBanned) {
user: confession.authorId, return false;
confessionId: confessionId,
method: BanReason.ById
});
this.saveFile();
return true;
}
}
} }
return false; const { authorId } = await this.client.confession.findFirstOrThrow({ where: { guildId, id: confessionId } });
await this.client.ban.create({ data: { guildId, authorId, confessionId, reason: BanReason.ById } });
return true;
} }
public addBanByUser(guildId: string, userId: string): boolean { public async addBanByUser(guildId: string, authorId: string): Promise<boolean> {
for (const guild of this.data) { const alreadyBanned = await this.isBannedByUser(guildId, authorId);
if (guild.id === guildId) {
// Only add the user to the ban list if they aren't banned already
!this.isBannedByUser(guildId, userId) &&
guild.settings.bans.push({
user: userId,
method: BanReason.ByUser
});
this.saveFile(); if (alreadyBanned) {
return true; return false;
}
} }
return false; await this.client.ban.create({ data: { guildId, authorId, reason: BanReason.ByUser } });
return true;
} }
// Attempts to pardon a user from a ban. If sucessfully completed, returns true, false if otherwise. // Attempts to pardon a user from a ban. If sucessfully completed, returns true, false if otherwise.
public removeBanById(guildId: string, confessionId: string): boolean { public async removeBanById(guildId: string, confessionId: string): Promise<boolean> {
for (const guild of this.data) { if (await this.isBannedById(guildId, confessionId)) {
if (guild.id === guildId) { return false;
if (this.getConfession(guildId, confessionId)) {
guild.settings.bans = guild.settings.bans.filter(ban => {
return (
ban.user !== this.getConfession(guildId, confessionId)?.authorId!
);
});
this.saveFile();
return true;
}
}
} }
return false; const { authorId } = await this.client.ban.findFirstOrThrow({ where: { guildId, confessionId } });
await this.client.ban.delete({ where: { guildId, confessionId, authorId } });
return true;
} }
public removeBanByUser(guildId: string, userId: string): boolean { public async removeBanByUser(guildId: string, authorId: string): Promise<boolean> {
for (const guild of this.data) { if (await this.isBannedByUser(guildId, authorId)) {
if (guild.id === guildId) { return false;
for (const ban of guild.settings.bans) {
if (ban.method === BanReason.ByUser && ban.user === userId) {
guild.settings.bans = guild.settings.bans.filter(ban => {
return ban.user !== userId;
});
this.saveFile();
return true;
}
}
}
} }
return false; await this.client.ban.delete({ where: { guildId, authorId } });
return true;
} }
} }

View file

@ -44,7 +44,6 @@ export interface GuildSettings {
export interface GuildData { export interface GuildData {
id: string; id: string;
versionNote?: "v0.1.1";
confessions: Confession[]; confessions: Confession[];
settings: GuildSettings; settings: GuildSettings;
} }