Skip to content

Commit 59b930e

Browse files
Feature/embeds (#20)
* fix: load dotenv in env checks * feat: add embed to message logic Switch rest client to @discordjs/rest Support sending files in logging messages * feat: Add embed message "generation" Add message generation flow Add quick sending method Update embed representation to allow all settable fields * feat: pretty print embed files * fix: check if embed exceeds limits on save * feat: add check for embed limits on send * fix: use correct error for embed limit * feat: check field length when adding embed field * feat: check for title and description when editing / adding an embed * fix(style): unused import * feat: add embed options to editing Make content nullable in prisma schema Generate embed from existing embed on edit button Make embed field a modal paragraph - the length for a short isn't checked Add edit action handling for message generation Store messageId in cache Adjust cache handling - no key throws an error - and add TTL of one day Adjust message generation for message generation - add edit type and message link Adjust handling for missing content in log embeds Include embed in return for checkEditPossible to allow embed caching for message generation * feat: add embeds to adding messages * chore: add to todo * chore: remove unneeded comment
1 parent 743632f commit 59b930e

File tree

36 files changed

+2674
-379
lines changed

36 files changed

+2674
-379
lines changed

package-lock.json

Lines changed: 136 additions & 210 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
"format:check": "prettier --check ."
2020
},
2121
"dependencies": {
22-
"@prisma/client": "^3.9.2",
22+
"@discordjs/rest": "^0.5.0",
23+
"@prisma/client": "^3.15.2",
2324
"@sentry/node": "^6.19.6",
2425
"@sinclair/typebox": "^0.20.5",
2526
"axios": "^0.26.0",
26-
"detritus-client-rest": "^0.10.5",
2727
"discord-interactions": "^2.4.1",
2828
"env-schema": "^4.0.0",
2929
"fastify": "^3.22.0",
@@ -60,7 +60,7 @@
6060
"eslint-config-prettier": "^8.5.0",
6161
"eslint-plugin-simple-import-sort": "^7.0.0",
6262
"prettier": "2.6.1",
63-
"prisma": "^3.9.2",
63+
"prisma": "^3.15.2",
6464
"tap": "^16.0.1",
6565
"ts-node": "^10.5.0",
6666
"tsc-watch": "^4.6.0",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
-- AlterTable
2+
ALTER TABLE "Message" ADD COLUMN "internalId" SERIAL NOT NULL,
3+
ADD CONSTRAINT "Message_pkey" PRIMARY KEY ("internalId");
4+
5+
-- CreateTable
6+
CREATE TABLE "MessageEmbed" (
7+
"id" SERIAL NOT NULL,
8+
"title" TEXT,
9+
"description" TEXT,
10+
"url" TEXT,
11+
"authorName" TEXT,
12+
"footerText" TEXT,
13+
"timestamp" TIMESTAMP(3),
14+
"color" INTEGER,
15+
"messageId" INTEGER NOT NULL,
16+
17+
CONSTRAINT "MessageEmbed_pkey" PRIMARY KEY ("id")
18+
);
19+
20+
-- CreateTable
21+
CREATE TABLE "EmbedField" (
22+
"id" SERIAL NOT NULL,
23+
"name" TEXT NOT NULL,
24+
"value" TEXT NOT NULL,
25+
"inline" BOOLEAN NOT NULL DEFAULT false,
26+
"embedId" INTEGER NOT NULL,
27+
28+
CONSTRAINT "EmbedField_pkey" PRIMARY KEY ("id")
29+
);
30+
31+
-- CreateIndex
32+
CREATE UNIQUE INDEX "MessageEmbed_messageId_key" ON "MessageEmbed"("messageId");
33+
34+
-- AddForeignKey
35+
ALTER TABLE "MessageEmbed" ADD CONSTRAINT "MessageEmbed_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("internalId") ON DELETE CASCADE ON UPDATE NO ACTION;
36+
37+
-- AddForeignKey
38+
ALTER TABLE "EmbedField" ADD CONSTRAINT "EmbedField_embedId_fkey" FOREIGN KEY ("embedId") REFERENCES "MessageEmbed"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "MessageEmbed" ADD COLUMN "authorIconUrl" TEXT,
3+
ADD COLUMN "authorUrl" TEXT,
4+
ADD COLUMN "footerIconUrl" TEXT,
5+
ADD COLUMN "thumbnailUrl" TEXT;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Message" ALTER COLUMN "content" DROP NOT NULL;

prisma/schema.prisma

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,79 @@ datasource db {
77
url = env("DATABASE_URL")
88
}
99

10-
1110
model Channel {
12-
id BigInt @id
13-
permissions Json?
14-
webhookId BigInt?
15-
webhookToken String? @db.VarChar(255) /// @encrypted
16-
guildId BigInt
17-
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade, onUpdate: NoAction)
18-
messages Message[]
19-
20-
}
11+
id BigInt @id
12+
permissions Json?
13+
webhookId BigInt?
14+
webhookToken String? @db.VarChar(255) /// @encrypted
15+
guildId BigInt
16+
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade, onUpdate: NoAction)
17+
messages Message[]
2118
19+
}
2220

2321
model Guild {
24-
id BigInt @id
25-
logChannelId BigInt?
26-
permissions Json?
27-
beforeMigration Boolean @default(false) // This indicates if the guild existed before the migration to storing message content.
22+
id BigInt @id
23+
logChannelId BigInt?
24+
permissions Json?
25+
beforeMigration Boolean @default(false) // This indicates if the guild existed before the migration to storing message content.
2826
// If it was it gains access to adding any previous bot sent message to the database.
29-
messages Message[]
30-
channels Channel[]
27+
messages Message[]
28+
channels Channel[]
3129
}
3230

3331
model Message {
34-
id BigInt
35-
guildId BigInt
36-
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade, onUpdate: NoAction)
37-
channelId BigInt
38-
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade, onUpdate: NoAction)
39-
content String /// @encrypted
40-
editedAt DateTime
41-
editedBy BigInt
42-
deleted Boolean @default(false)
43-
addedByUser Boolean @default(false) // This indicates if the message was a message previously sent by the bot, and then added to the database.
32+
internalId Int @id @default(autoincrement()) // Exists to allow for storage of message history and ease of relating
33+
id BigInt
34+
guildId BigInt
35+
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade, onUpdate: NoAction)
36+
channelId BigInt
37+
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade, onUpdate: NoAction)
38+
content String? /// @encrypted
39+
editedAt DateTime
40+
editedBy BigInt
41+
deleted Boolean @default(false)
42+
addedByUser Boolean @default(false) // This indicates if the message was a message previously sent by the bot, and then added to the database.
4443
45-
@@unique([id, editedAt])
44+
embed MessageEmbed?
4645
47-
}
4846
47+
@@unique([id, editedAt])
48+
}
4949

50+
model MessageEmbed {
51+
id Int @id @default(autoincrement())
52+
title String? /// @encrypted
53+
description String? /// @encrypted
54+
url String? /// @encrypted
55+
authorName String? /// @encrypted
56+
authorUrl String? /// @encrypted
57+
authorIconUrl String? /// @encrypted
58+
footerText String? /// @encrypted
59+
footerIconUrl String? /// @encrypted
60+
thumbnailUrl String? /// @encrypted
61+
timestamp DateTime?
62+
color Int?
63+
fields EmbedField[]
64+
messageId Int @unique
65+
message Message @relation(fields: [messageId], references: [internalId], onDelete: Cascade, onUpdate: NoAction)
66+
}
5067

68+
model EmbedField {
69+
id Int @id @default(autoincrement())
70+
name String /// @encrypted
71+
value String /// @encrypted
72+
inline Boolean @default(false)
73+
embedId Int
74+
embed MessageEmbed @relation(fields: [embedId], references: [id], onDelete: Cascade, onUpdate: NoAction)
75+
}
5176

5277
model User {
53-
id BigInt @id
54-
oauthToken String? /// @encrypted
78+
id BigInt @id
79+
oauthToken String? /// @encrypted
5580
oauthTokenExpiration DateTime?
56-
refreshToken String? /// @encrypted
57-
staff Boolean @default(false)
81+
refreshToken String? /// @encrypted
82+
staff Boolean @default(false)
5883
}
5984

6085
enum ReportStatus {
@@ -67,35 +92,35 @@ enum ReportStatus {
6792
}
6893

6994
model Report {
70-
id BigInt @id @default(autoincrement())
71-
userId BigInt
72-
content String /// @encrypted
73-
messageId BigInt
74-
guildId BigInt
75-
channelId BigInt
76-
reportedAt DateTime
77-
resolvedAt DateTime? // Report should be resolved closed if this is set
78-
status ReportStatus @default(Pending)
79-
userReportReason String /// @encrypted
95+
id BigInt @id @default(autoincrement())
96+
userId BigInt
97+
content String /// @encrypted
98+
messageId BigInt
99+
guildId BigInt
100+
channelId BigInt
101+
reportedAt DateTime
102+
resolvedAt DateTime? // Report should be resolved closed if this is set
103+
status ReportStatus @default(Pending)
104+
userReportReason String /// @encrypted
80105
staffResolvedReasonId BigInt?
81-
staffResolvedReason ReportReason? @relation(fields: [staffResolvedReasonId], references: [id], onDelete: Cascade, onUpdate: NoAction)
82-
messages ReportMessage[]
83-
}
106+
staffResolvedReason ReportReason? @relation(fields: [staffResolvedReasonId], references: [id], onDelete: Cascade, onUpdate: NoAction)
107+
messages ReportMessage[]
108+
}
84109

85-
model ReportReason {
110+
model ReportReason {
86111
// Set by staff when closing the ticket
87-
id BigInt @id @default(autoincrement())
88-
name String
112+
id BigInt @id @default(autoincrement())
113+
name String
89114
description String
90-
reports Report[]
115+
reports Report[]
91116
}
92117

93118
model ReportMessage {
94119
// Communication between the user and staff - doesn't include the inital report
95-
id BigInt @id @default(autoincrement())
96-
authorId BigInt
120+
id BigInt @id @default(autoincrement())
121+
authorId BigInt
97122
fromStaff Boolean // If the author of the message was staff
98-
content String /// @encrypted
99-
reportId BigInt
100-
report Report @relation(fields: [reportId], references: [id], onDelete: Cascade, onUpdate: NoAction)
101-
}
123+
content String /// @encrypted
124+
reportId BigInt
125+
report Report @relation(fields: [reportId], references: [id], onDelete: Cascade, onUpdate: NoAction)
126+
}

src/discord_commands/global.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"type": 7,
5757
"name": "channel",
5858
"required": false,
59-
"description": "The channel to manage / view permissions of the target on. Leave this blank to manage permissions on the entire server",
59+
"description": "The channel to manage / view permissions of the target on. Leave this blank for the entire server",
6060
"channel_types": [0, 5]
6161
}
6262
]
@@ -92,7 +92,7 @@
9292
"type": 7,
9393
"name": "channel",
9494
"required": false,
95-
"description": "The channel to setup permissions of the target on. Leave this blank to setup permissions on the entire server",
95+
"description": "The channel to setup permissions of the target on. Leave this blank for the entire server",
9696
"channel_types": [0, 5]
9797
}
9898
]
@@ -135,6 +135,12 @@
135135
"description": "Channel to send the message to",
136136
"channel_types": [0, 5, 10, 11, 12],
137137
"required": true
138+
},
139+
{
140+
"name": "content-only",
141+
"type": 5,
142+
"description": "Send a message quickly by only sending content",
143+
"required": false
138144
}
139145
]
140146
},

src/errors.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ enum InteractionOrRequestFinalStatus {
2828
TAG_NOT_FOUND,
2929
NO_PERMISSIONS_PRESET_SELECTED,
3030
MANAGEMENT_PERMISSIONS_CANNOT_BE_SET_ON_CHANNEL_LEVEL,
31+
MESSAGE_HAS_NO_CONTENT,
32+
ATTEMPTING_TO_SEND_WHEN_NO_CONTENT_SET,
33+
EMBED_VALUE_EDITING_MALFORMED,
34+
EMBED_EDITING_MISSING_REQUIRED_VALUE,
35+
EMBED_REQUIRES_TITLE_OR_DESCRIPTION,
36+
MESSAGE_GENERATION_CACHE_NOT_FOUND,
37+
MIGRATION_ATTEMPTED_ON_MESSAGE_WITH_MULTIPLE_EMBEDS,
3138
GENERIC_EXPECTED_PERMISSIONS_FAILURE = 3000,
3239
USER_MISSING_DISCORD_PERMISSION,
3340
BOT_MISSING_DISCORD_PERMISSION,
@@ -42,6 +49,7 @@ enum InteractionOrRequestFinalStatus {
4249
MAX_USER_PERMISSIONS,
4350
MAX_ROLE_CHANNEL_PERMISSIONS,
4451
MAX_USER_CHANNEL_PERMISSIONS,
52+
EMBED_EXCEEDS_DISCORD_LIMITS,
4553
GENERIC_UNEXPECTED_FAILURE = 6000,
4654
INTERACTION_TYPE_MISSING_HANDLER,
4755
APPLICATION_COMMAND_TYPE_MISSING_HANDLER,
@@ -62,6 +70,12 @@ enum InteractionOrRequestFinalStatus {
6270
CREATE_WEBHOOK_RESULT_MISSING_TOKEN,
6371
ROLE_NOT_IN_CACHE,
6472
PERMISSIONS_CANNOT_CROSSOVER_WHEN_UPDATING,
73+
TOO_MANY_EMBEDS,
74+
MESSAGE_NOT_FOUND_IN_DATABASE_AFTER_CHECKS_DONE,
75+
FIELD_SELECT_OUT_OF_INDEX,
76+
EMBED_EDITING_MISSING_DISCORD_REQUIRED_VALUE,
77+
MESSAGE_ID_MISSING_ON_MESSAGE_EDIT_CACHE,
78+
INTERACTION_TIMED_OUT_HTTP,
6579
}
6680

6781
class CustomError extends Error {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ instance.setErrorHandler(async (error, request, reply) => {
5252
// These are plugins that are separate from versioning
5353
await instance.register(prismaPlugin);
5454
await instance.register(discordRestPlugin, {
55-
detritus: { token: instance.envVars.DISCORD_TOKEN },
55+
discord: { token: instance.envVars.DISCORD_TOKEN },
5656
});
5757
await instance.register(redisRestPlugin, {
5858
redis: {

src/interactions/buttons/delete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default async function handleDeleteButton(
4444
title: "Delete Message",
4545
url: `https://discord.com/channels/${interaction.guild_id}/${databaseMessage.channelId}/${messageId}`,
4646
description: `Are you sure you want to delete this message?\n**Content:**\n\n${
47-
content.length > maxLength
47+
content !== null && content.length > maxLength
4848
? `${content.substring(0, maxLength)}...`
4949
: content
5050
}`,

0 commit comments

Comments
 (0)