Skip to content

Commit 0f8fcc0

Browse files
committed
Make template stuff uh... cleaner (?)
1 parent c130ef1 commit 0f8fcc0

File tree

10 files changed

+101
-59
lines changed

10 files changed

+101
-59
lines changed
Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,14 @@
11
/* eslint-disable @typescript-eslint/explicit-function-return-type */
22
import { Color, Snowflake } from "#common/schema/general.ts";
3-
import { template } from "#common/schema/template.ts";
3+
import { template, type Template } from "#common/schema/template.ts";
44
import type { ParameterRecord, TemplateSchema } from "#common/template/index.ts";
55
import { MessageFlags } from "oceanic.js";
66
import { TomlDate } from "smol-toml";
77
import { z } from "zod/v4";
88

9-
export const MessageLiteral = message(() => z.string());
10-
export type MessageLiteral = z.infer<typeof MessageLiteral>;
11-
12-
export function messageTemplate<S extends TemplateSchema>(schema: S) {
13-
const result = message(markdown => template(schema, markdown))
14-
.transform(({ content, embeds, ...message }) => (params: ParameterRecord<S>) => ({
15-
content: content?.(params),
16-
embeds: embeds?.map(
17-
({ author, description, fields, footer, image, thumbnail, title, url, ...embed }) => ({
18-
author: author !== undefined
19-
? {
20-
name: author.name(params),
21-
url: author.url?.(params),
22-
iconURL: author.iconURL?.(params),
23-
}
24-
: undefined,
25-
description: description?.(params),
26-
fields: fields?.map(({ name, value, ...field }) => ({
27-
name: name?.(params),
28-
value: value?.(params),
29-
...field
30-
})) ?? [],
31-
footer: footer !== undefined
32-
? { text: footer.text(params), icon: footer.icon(params) }
33-
: undefined,
34-
image: image !== undefined ? { url: image.url(params) } : undefined,
35-
thumbnail: thumbnail !== undefined ? { url: thumbnail.url(params) } : undefined,
36-
title: title?.(params),
37-
url: url?.(params),
38-
...embed
39-
})
40-
) ?? [],
41-
...message
42-
}));
43-
44-
// @ts-expect-error avoid parsing constantly
45-
result.prefault = content => {
46-
const parsed = result.parse(content);
47-
return result.default(() => parsed);
48-
};
9+
type Message<Z extends z.ZodType> = z.output<ReturnType<typeof message<Z>>>;
4910

50-
return result;
51-
}
52-
53-
function message<Z extends z.ZodTypeAny>(stringType: (markdown: boolean) => Z) {
11+
function message<Z extends z.ZodType>(stringType: (markdown: boolean) => Z) {
5412
return z.strictObject({
5513
content: stringType(true),
5614
allowed_mentions: z.strictObject({
@@ -59,7 +17,7 @@ function message<Z extends z.ZodTypeAny>(stringType: (markdown: boolean) => Z) {
5917
users: z.union([z.boolean(), Snowflake.array().max(100)]),
6018
roles: z.union([z.boolean(), Snowflake.array().max(100)]),
6119
}).partial().transform(({ replied_user, ...input }) => ({
62-
epliedUser: replied_user, ...input
20+
repliedUser: replied_user, ...input
6321
})),
6422
embeds: embed(stringType).array(),
6523
silent: z.boolean(),
@@ -70,6 +28,8 @@ function message<Z extends z.ZodTypeAny>(stringType: (markdown: boolean) => Z) {
7028
}));
7129
}
7230

31+
type Embed<Z extends z.ZodType> = z.output<ReturnType<typeof embed<Z>>>;
32+
7333
function embed<Z extends z.ZodType>(stringType: (markdown: boolean) => Z) {
7434
return z.strictObject({
7535
title: stringType(true),
@@ -93,3 +53,75 @@ function embed<Z extends z.ZodType>(stringType: (markdown: boolean) => Z) {
9353
}).partial();
9454
}
9555

56+
export const MessageLiteral = message(() => z.string());
57+
export type MessageLiteral = z.infer<typeof MessageLiteral>;
58+
59+
export function messageTemplate<S extends TemplateSchema>(schema: S) {
60+
const result = message(markdown => template(schema, markdown))
61+
.transform(template => {
62+
return {
63+
apply(params: ParameterRecord<S>) {
64+
return applyMessageTemplate(template, params);
65+
}
66+
};
67+
});
68+
69+
// @ts-expect-error avoid parsing constantly
70+
result.prefault = content => {
71+
const parsed = result.parse(content);
72+
return result.default(parsed);
73+
};
74+
75+
return result;
76+
}
77+
78+
function applyMessageTemplate<S extends TemplateSchema>(template: Message<Template<S>>, params: ParameterRecord<S>) {
79+
return {
80+
...template,
81+
content: template.content?.apply(params),
82+
embeds: template.embeds?.map(embed => applyEmbedTemplate(embed, params)),
83+
};
84+
}
85+
86+
function applyEmbedTemplate<S extends TemplateSchema>(template: Embed<Template<S>>, params: ParameterRecord<S>) {
87+
const author = template.author !== undefined
88+
? {
89+
name: template.author.name.apply(params),
90+
url: template.author.url?.apply(params),
91+
iconURL: template.author.iconURL?.apply(params),
92+
}
93+
: undefined;
94+
95+
const fields = template.fields?.map(({ name, value, ...field }) => ({
96+
name: name?.apply(params),
97+
value: value?.apply(params),
98+
...field
99+
}));
100+
101+
const footer = template.footer !== undefined
102+
? {
103+
text: template.footer.text.apply(params),
104+
icon: template.footer.icon.apply(params),
105+
}
106+
: undefined;
107+
108+
const image = template.image !== undefined
109+
? { url: template.image.url.apply(params) }
110+
: undefined;
111+
112+
const thumbnail = template.thumbnail !== undefined
113+
? { url: template.thumbnail.url.apply(params) }
114+
: undefined;
115+
116+
return ({
117+
...template,
118+
author,
119+
description: template.description?.apply(params),
120+
fields,
121+
footer,
122+
image,
123+
thumbnail,
124+
title: template.title?.apply(params),
125+
url: template.url?.apply(params)
126+
});
127+
}

backend/src/common/schema/template.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import { parseTemplate, type TemplateSchema } from "#common/template/index.ts";
33
import { z } from "zod/v4";
44

5+
export type Template<S extends TemplateSchema> = ReturnType<typeof template<S>>;
6+
57
export function template<S extends TemplateSchema>(schema: S, allowEscape = true) {
68
return z.string().transform((input, context) => {
79
const result = parseTemplate(input, schema, allowEscape);

backend/src/common/template/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
import { formatTokens } from "#common/template/formatting.ts";
1313
import { parseTemplateTokens, TokenType } from "#common/template/parsing.ts";
1414

15+
interface TemplateWrapper<S extends TemplateSchema> {
16+
apply: ((params: ParameterRecord<S>) => string);
17+
}
18+
1519
/**
1620
* @returns A function to call to format data with the template,
1721
* or an error denoting something that went wrong with parsing.
1822
*/
19-
export function parseTemplate<S extends TemplateSchema>(template: string, schema: S, allowEscape = true): ((params: ParameterRecord<S>) => string) | string {
23+
export function parseTemplate<S extends TemplateSchema>(template: string, schema: S, allowEscape = true): TemplateWrapper<S> | string {
2024
// just a typed wrapper
2125
const tokens = parseTemplateTokens(template, schema);
2226

@@ -31,7 +35,11 @@ export function parseTemplate<S extends TemplateSchema>(template: string, schema
3135
return () => value;
3236
}
3337

34-
return params => formatTokens(params, tokens, allowEscape);
38+
return {
39+
apply(params) {
40+
return formatTokens(params, tokens, allowEscape);
41+
}
42+
};
3543
}
3644

3745
export type TemplateSchema = Record<string, ParameterType>;

backend/src/plugin/logging/logger/members.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async function handleAdd(member: Member): Promise<void> {
2020
if (!member_join)
2121
continue;
2222

23-
await logWithLogger(logger, member.guild, member_join.message({
23+
await logWithLogger(logger, member.guild, member_join.message.apply({
2424
user: member,
2525
user_avatar: member.avatarURL(),
2626
user_created_at: member.createdAt,
@@ -44,7 +44,7 @@ async function handleRemove(user: Member | User, guild: Guild | Uncached): Promi
4444
if (!member_leave)
4545
continue;
4646

47-
await logWithLogger(logger, guild, member_leave.message({
47+
await logWithLogger(logger, guild, member_leave.message.apply({
4848
user: user,
4949
user_avatar: user.avatarURL(),
5050
user_created_at: user.createdAt,

backend/src/plugin/logging/logger/messages.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async function handleUpdate(message: Message): Promise<void> {
8080
content: message.content,
8181
});
8282

83-
await logWithLogger(logger, message.guild, message_edit.message({
83+
await logWithLogger(logger, message.guild, message_edit.message.apply({
8484
author: message.author,
8585
author_avatar: message.author.avatarURL(),
8686
old_content: entry?.content,
@@ -113,7 +113,7 @@ async function handleDelete(message: PossiblyUncachedMessage): Promise<void> {
113113
if (!message_delete)
114114
continue;
115115

116-
await logWithLogger(logger, message.guild, message_delete.message({
116+
await logWithLogger(logger, message.guild, message_delete.message.apply({
117117
author: { id: entry.authorID, tag: entry.authorName },
118118
author_avatar: avatarURL,
119119
content: entry.content,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async function handleCreate(guild: Guild | Uncached, entry: AuditLogEntry): Prom
6060

6161
user ??= await fetchUserCached(entry.userID);
6262

63-
await logWithLogger(logger, guild, role_create.message({
63+
await logWithLogger(logger, guild, role_create.message.apply({
6464
user,
6565
user_avatar: user.avatarURL(),
6666
role: { id: entry.targetID, name: changes.name!.new! },
@@ -102,7 +102,7 @@ async function handleUpdate(guild: Guild | Uncached, entry: AuditLogEntry): Prom
102102

103103
user ??= await fetchUserCached(entry.userID);
104104

105-
await logWithLogger(logger, guild, role_update.message({
105+
await logWithLogger(logger, guild, role_update.message.apply({
106106
user,
107107
user_avatar: user.avatarURL(),
108108
role: { id: entry.targetID, name },
@@ -145,7 +145,7 @@ async function handleDelete(guild: Guild | Uncached, entry: AuditLogEntry): Prom
145145

146146
user ??= await fetchUserCached(entry.userID);
147147

148-
await logWithLogger(logger, guild, role_delete.message({
148+
await logWithLogger(logger, guild, role_delete.message.apply({
149149
user,
150150
user_avatar: user.avatarURL(),
151151
role: { id: entry.targetID, name: changes.name!.old! },

backend/src/plugin/moderation/command/action/ban.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default defineCommand({
4141
async run(context, args, { config }) {
4242
const sendDirectMessage = args.dm ?? config.ban.send_direct_message;
4343
const directMessage = sendDirectMessage
44-
? config.ban.direct_message({
44+
? config.ban.direct_message.apply({
4545
server: context.guild,
4646
moderator: context.user,
4747
reason: args.reason ?? undefined,

backend/src/plugin/moderation/command/action/kick.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default defineCommand({
3838
const sendDirectMessage = args.dm ?? config.ban.send_direct_message;
3939
const directMessage: CreateMessageOptions | undefined =
4040
sendDirectMessage ?
41-
config.ban.direct_message({
41+
config.ban.direct_message.apply({
4242
server: context.guild,
4343
moderator: context.user,
4444
reason: args.reason ?? undefined,

backend/src/plugin/moderation/command/action/timeout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default defineCommand({
4444
async run(context, args, { config }) {
4545
const sendDirectMessage = args.dm ?? config.timeout.send_direct_message;
4646
const directMessage = sendDirectMessage
47-
? config.timeout.direct_message({
47+
? config.timeout.direct_message.apply({
4848
server: context.guild,
4949
moderator: context.user,
5050
reason: args.reason ?? undefined,

backend/src/plugin/moderation/command/action/warn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default defineCommand({
3636
async run(context, args, { config }): Promise<void> {
3737
const sendDirectMessage = args.dm ?? config.ban.send_direct_message;
3838
const directMessage = sendDirectMessage
39-
? config.warn.direct_message({
39+
? config.warn.direct_message.apply({
4040
server: context.guild,
4141
moderator: context.user,
4242
reason: args.reason ?? undefined,

0 commit comments

Comments
 (0)