Skip to content

Commit 9741033

Browse files
committed
Support logging tag changes
1 parent c0cb5dc commit 9741033

14 files changed

Lines changed: 301 additions & 58 deletions

File tree

backend/src/extensionPoint.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const enum EventListenerPhase {
7373

7474
type EventListener<T extends unknown[]> = (...args: T) => Awaitable<void>;
7575

76+
// FIXME: this is overkill for most usages
7677
export function makeEventExtensionPoint<T extends unknown[]>(): ((
7778
listener: EventListener<T>,
7879
phase?: EventListenerPhase,

backend/src/plugin/logging/config/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import {
2121
RoleDeleteEvent,
2222
RoleUpdateEvent,
2323
} from "#plugin/logging/config/roles.ts";
24+
import {
25+
TagCreateEvent,
26+
TagDeleteEvent,
27+
TagEditEvent,
28+
} from "#plugin/logging/config/tags.ts";
2429
import { z } from "zod";
2530

2631
export function eventConfig<T extends z.ZodType>(
@@ -39,6 +44,7 @@ export function eventConfig<T extends z.ZodType>(
3944

4045
export const LoggerConfig = z.strictObject({
4146
channel: Snowflake,
47+
// FIXME: wrong case!
4248
displayName: z.string().default(APP_NAME + " Logging"),
4349
avatar: z.url().optional(),
4450
events: z
@@ -57,6 +63,10 @@ export const LoggerConfig = z.strictObject({
5763
user_unban: UserUnbanEvent,
5864
user_kick: UserKickEvent,
5965
user_warn: UserWarnEvent,
66+
67+
tag_create: TagCreateEvent,
68+
tag_edit: TagEditEvent,
69+
tag_delete: TagDeleteEvent,
6070
})
6171
.describe(
6272
"Customize what happens when events happen in the server — set them to true to use the default presentation",

backend/src/plugin/logging/config/roles.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const RoleUpdateEvent = eventConfig(
4545
{
4646
embeds: [
4747
{
48-
title: "Role Updated",
48+
title: "Updated Role",
4949
author: { name: "{{actor.tag}}", icon_url: "{{actor.avatar}}" },
5050
description: "{{new_role.mention}}",
5151
fields: [
@@ -83,7 +83,7 @@ export const RoleDeleteEvent = eventConfig(
8383
{
8484
embeds: [
8585
{
86-
title: "Role Deleted",
86+
title: "Deleted Role",
8787
author: { name: "{{actor.tag}}", icon_url: "{{actor.avatar}}" },
8888
fields: [
8989
{ name: "Name", value: "{{role.name}}" },
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { messageTemplate } from "#common/schema/message.ts";
2+
import { GuildView } from "#common/template/guild.ts";
3+
import { UserView } from "#common/template/user.ts";
4+
import { eventConfig } from "#plugin/logging/config/index.ts";
5+
import { m } from "mousetache";
6+
7+
export const TagView = m.object({
8+
name: m.terminal(),
9+
content: m.terminal(),
10+
});
11+
12+
export const TagCreateEvent = eventConfig(
13+
messageTemplate(
14+
m.object({
15+
guild: GuildView,
16+
actor: UserView,
17+
tag: TagView,
18+
}),
19+
),
20+
{
21+
embeds: [
22+
{
23+
title: "Created Tag",
24+
author: {
25+
name: "{{actor.tag}}",
26+
icon_url: "{{actor.avatar}}",
27+
},
28+
fields: [
29+
{
30+
name: "Name",
31+
value: "{{tag.name}}",
32+
},
33+
{
34+
name: "Content",
35+
value: "{{tag.content}}",
36+
},
37+
],
38+
color: "green",
39+
footer: { text: "Actor ID: {{actor.id}}" },
40+
},
41+
],
42+
},
43+
);
44+
45+
export const TagEditEvent = eventConfig(
46+
messageTemplate(
47+
m.object({
48+
guild: GuildView,
49+
actor: UserView,
50+
51+
old_tag: TagView,
52+
new_tag: TagView,
53+
54+
name_changed: m.terminal(),
55+
content_changed: m.terminal(),
56+
}),
57+
),
58+
{
59+
embeds: [
60+
{
61+
title: "Edited Tag",
62+
author: {
63+
name: "{{actor.tag}}",
64+
icon_url: "{{actor.avatar}}",
65+
},
66+
fields: [
67+
{
68+
name: "Name",
69+
value: "{{#name_changed}}{{old_tag.name}} → {{new_tag.name}}{{/name_changed}}{{^name_changed}}{{new_tag.name}}{{/name_changed}}",
70+
},
71+
{
72+
name: "Old Content",
73+
value: "{{#content_changed}}{{old_tag.content}}{{/content_changed}}",
74+
},
75+
{
76+
name: "New Content",
77+
value: "{{#content_changed}}{{new_tag.content}}{{/content_changed}}",
78+
},
79+
],
80+
color: "yellow",
81+
footer: { text: "Actor ID: {{actor.id}}" },
82+
},
83+
],
84+
},
85+
);
86+
87+
export const TagDeleteEvent = eventConfig(
88+
messageTemplate(
89+
m.object({
90+
guild: GuildView,
91+
actor: UserView,
92+
tag: TagView,
93+
}),
94+
),
95+
{
96+
embeds: [
97+
{
98+
title: "Deleted Tag",
99+
author: {
100+
name: "{{actor.tag}}",
101+
icon_url: "{{actor.avatar}}",
102+
},
103+
fields: [
104+
{
105+
name: "Name",
106+
value: "{{tag.name}}",
107+
},
108+
{
109+
name: "Content",
110+
value: "{{tag.content}}",
111+
},
112+
],
113+
color: "red",
114+
footer: { text: "Actor ID: {{actor.id}}" },
115+
},
116+
],
117+
},
118+
);

backend/src/plugin/logging/helper/logging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type EventConfigView<T extends EventConfig> = Parameters<
1111
Exclude<T, false>["render"]
1212
>[0];
1313

14+
// FIXME: the generics are very broken!
1415
export async function logEvent<T extends EventConfig>(
1516
ctx: SquirrelDiscordContext,
1617
guild: Guild,

backend/src/plugin/logging/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import memberLogger from "#plugin/logging/logger/members.ts";
66
import messageLogger from "#plugin/logging/logger/messages.ts";
77
import modEventLogger from "#plugin/logging/logger/modEvents.ts";
88
import roleLogger from "#plugin/logging/logger/roles.ts";
9+
import tagsLogger from "#plugin/logging/logger/tags.ts";
910

1011
export const loggingConfigStore = new ConfigStore(LoggingConfig);
1112

@@ -31,5 +32,6 @@ export default definePlugin({
3132
...roleLogger,
3233
...memberLogger,
3334
...modEventLogger,
35+
...tagsLogger,
3436
],
3537
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { makeGuildView } from "#common/template/guild.ts";
2+
import { makeMemberUserView } from "#common/template/user.ts";
3+
import type { SquirrelDiscordContext } from "#discord/index.ts";
4+
import { logEvent } from "#plugin/logging/helper/logging.ts";
5+
import {
6+
onTagCreated,
7+
onTagDeleted,
8+
onTagEdited,
9+
} from "#plugin/tags/public/extensionPoints.ts";
10+
import type { Tag } from "#plugin/tags/public/tag.ts";
11+
import type { Guild, Member } from "oceanic.js";
12+
13+
export default [
14+
onTagCreated(handleCreate),
15+
onTagEdited(handleEdit),
16+
onTagDeleted(handleDelete),
17+
];
18+
19+
async function handleCreate(
20+
ctx: SquirrelDiscordContext,
21+
guild: Guild,
22+
actor: Member,
23+
tag: Tag,
24+
): Promise<void> {
25+
await logEvent(ctx, guild, null, "tag_create", () => {
26+
return {
27+
guild: makeGuildView(guild),
28+
actor: makeMemberUserView(actor),
29+
tag,
30+
};
31+
});
32+
}
33+
34+
async function handleEdit(
35+
ctx: SquirrelDiscordContext,
36+
guild: Guild,
37+
actor: Member,
38+
oldTag: Tag,
39+
newTag: Tag,
40+
): Promise<void> {
41+
const nameChanged = oldTag.name !== newTag.name;
42+
const contentChanged = oldTag.content !== newTag.content;
43+
44+
if (!(nameChanged || contentChanged)) {
45+
return;
46+
}
47+
48+
await logEvent(ctx, guild, null, "tag_edit", () => {
49+
return {
50+
guild: makeGuildView(guild),
51+
actor: makeMemberUserView(actor),
52+
old_tag: oldTag,
53+
new_tag: newTag,
54+
name_changed: nameChanged,
55+
content_changed: contentChanged,
56+
};
57+
});
58+
}
59+
60+
async function handleDelete(
61+
ctx: SquirrelDiscordContext,
62+
guild: Guild,
63+
actor: Member,
64+
tag: Tag,
65+
): Promise<void> {
66+
await logEvent(ctx, guild, null, "tag_delete", () => {
67+
return {
68+
guild: makeGuildView(guild),
69+
actor: makeMemberUserView(actor),
70+
tag,
71+
};
72+
});
73+
}

backend/src/plugin/tags/command/tagCreate.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { escapeMarkdown } from "#common/discord/markdown.ts";
2+
import { moduleLogger } from "#common/logger/index.ts";
23
import { OptionType } from "#plugin/core/public/command.ts";
34
import { defineCommand } from "#plugin/core/public/extensionPoints.ts";
45
import { permissionsGuard } from "#plugin/core/public/helper/commandGuards.ts";
@@ -8,8 +9,11 @@ import {
89
MAX_TAG_NAME_LENGTH,
910
} from "#plugin/tags/constants.ts";
1011
import { tagsConfigStore } from "#plugin/tags/index.ts";
12+
import { onTagCreated } from "#plugin/tags/public/extensionPoints.ts";
1113
import { createTag } from "#plugin/tags/storage/tags.ts";
1214

15+
const logger = moduleLogger();
16+
1317
export default defineCommand({
1418
name: ["tagcreate", "createtag", "tagnew", "newtag"],
1519
description: "Create a new tag.",
@@ -40,21 +44,21 @@ export default defineCommand({
4044
(permissions) => permissions.tag_create,
4145
),
4246
async run(ctx, args) {
43-
const success = await createTag(
44-
ctx.squirrelCtx.db,
45-
ctx.guild.id,
46-
args.name,
47-
args.content,
48-
);
47+
const success = await createTag(ctx.squirrelCtx.db, ctx.guild.id, args);
4948

50-
if (success) {
51-
await ctx.respond(
52-
`${icons.success} Created tag '${escapeMarkdown(args.name)}'!`,
53-
);
54-
} else {
49+
if (!success) {
5550
await ctx.respond(
5651
`${icons.error} Tag '${escapeMarkdown(args.name)}' already exists!`,
5752
);
53+
return;
5854
}
55+
56+
onTagCreated
57+
.fire(ctx.squirrelCtx, ctx.guild, ctx.member, args)
58+
.catch((error) => logger.error?.("Error in onTagCreated", error));
59+
60+
await ctx.respond(
61+
`${icons.success} Created tag '${escapeMarkdown(args.name)}'!`,
62+
);
5963
},
6064
});

backend/src/plugin/tags/command/tagDelete.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { escapeMarkdown } from "#common/discord/markdown.ts";
2+
import { moduleLogger } from "#common/logger/index.ts";
23
import { OptionType } from "#plugin/core/public/command.ts";
34
import { defineCommand } from "#plugin/core/public/extensionPoints.ts";
45
import { permissionsGuard } from "#plugin/core/public/helper/commandGuards.ts";
56
import { icons } from "#plugin/core/public/icons.ts";
67
import { MAX_TAG_NAME_LENGTH } from "#plugin/tags/constants.ts";
7-
import { autocompleteTags } from "#plugin/tags/helper/command.ts";
8+
import { autocompleteTags } from "#plugin/tags/helper/autocompletion.ts";
89
import { tagsConfigStore } from "#plugin/tags/index.ts";
10+
import { onTagDeleted } from "#plugin/tags/public/extensionPoints.ts";
911
import { deleteTag } from "#plugin/tags/storage/tags.ts";
1012

13+
const logger = moduleLogger();
14+
1115
export default defineCommand({
1216
name: ["tagdelete", "deletetag", "tagdel", "deltag", "tagrm", "rmtag"],
1317
description: "Delete a tag.",
@@ -31,20 +35,25 @@ export default defineCommand({
3135
(permissions) => permissions.tag_delete,
3236
),
3337
async run(ctx, args) {
34-
const success = await deleteTag(
38+
const tag = await deleteTag(
3539
ctx.squirrelCtx.db,
3640
ctx.guild.id,
3741
args.name,
3842
);
3943

40-
if (success) {
41-
await ctx.respond(
42-
`${icons.success} Deleted tag '${escapeMarkdown(args.name)}'!`,
43-
);
44-
} else {
44+
if (tag === null) {
4545
await ctx.respond(
4646
`${icons.error} Tag '${escapeMarkdown(args.name)}' does not exist!`,
4747
);
48+
return;
4849
}
50+
51+
onTagDeleted
52+
.fire(ctx.squirrelCtx, ctx.guild, ctx.member, tag)
53+
.catch((error) => logger.error?.("Error in onTagDeleted", error));
54+
55+
await ctx.respond(
56+
`${icons.success} Deleted tag '${escapeMarkdown(args.name)}'!`,
57+
);
4958
},
5059
});

0 commit comments

Comments
 (0)