Skip to content

Commit 5eb6ccb

Browse files
committed
feat: add user settings command for creating and displaying the link created by the context menu
1 parent dec1d3b commit 5eb6ccb

File tree

11 files changed

+2331
-1838
lines changed

11 files changed

+2331
-1838
lines changed

packages/bot/src/client.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as fs from "node:fs";
22
import path from "node:path";
33
import type { StatisticalTemplate } from "@dicelette/core";
4-
import type { BotStatus, CriticalCount, GuildData, UserDatabase } from "@dicelette/types";
4+
import type {
5+
BotStatus,
6+
CriticalCount,
7+
GuildData,
8+
UserDatabase,
9+
UserSettings,
10+
} from "@dicelette/types";
511
import { logger } from "@dicelette/utils";
612
import * as Djs from "discord.js";
713
import Enmap, { type EnmapOptions } from "enmap";
@@ -46,6 +52,8 @@ export class EClient extends Djs.Client {
4652
*/
4753
public statusPath = path.resolve("./data/status.json");
4854

55+
public userSettings: Enmap<string, UserSettings, unknown>;
56+
4957
constructor(options: Djs.ClientOptions) {
5058
super(options);
5159

@@ -63,6 +71,13 @@ export class EClient extends Djs.Client {
6371
name: "criticalCount",
6472
});
6573

74+
this.userSettings = new Enmap({
75+
autoFetch: true,
76+
cloneLevel: "deep",
77+
fetchAll: false,
78+
name: "userSettings",
79+
});
80+
6681
//read status from files in ./data folder
6782
if (fs.existsSync(this.statusPath)) {
6883
const data = fs.readFileSync(this.statusPath, "utf-8");

packages/bot/src/commands/admin/configuration/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import { deleteAfter, linkToLog, setContextLink, timestamp } from "./results";
1111
import { allowSelfRegistration } from "./self_registration";
1212
import { stripOOC } from "./strip_ooc";
1313
import "discord_ext";
14+
import {
15+
createLinksCmdOptions,
16+
getTemplateValues,
17+
setTemplate,
18+
} from "../../userSettings";
1419
import { editMeCommand } from "./editMe";
1520

1621
export const configuration = {
@@ -301,6 +306,16 @@ export const configuration = {
301306
.setDescriptions("editMe.banner.description")
302307
.setRequired(false)
303308
)
309+
)
310+
/**
311+
* Create links
312+
*/
313+
.addSubcommandGroup((group) =>
314+
createLinksCmdOptions(
315+
group
316+
.setNames("userSettings.createLink.title")
317+
.setDescriptions("userSettings.createLink.description")
318+
)
304319
),
305320
async execute(interaction: Djs.ChatInputCommandInteraction, client: EClient) {
306321
if (!interaction.guild) return;
@@ -326,6 +341,12 @@ export const configuration = {
326341
return dice(options, client, ul, interaction);
327342
}
328343
break;
344+
case t("userSettings.createLink.title"):
345+
if (subcommand === t("userSettings.createLink.format.name"))
346+
return await setTemplate(client, interaction, true);
347+
if (subcommand === t("userSettings.createLink.display.name"))
348+
return await getTemplateValues(client, ul, interaction, true);
349+
break;
329350
}
330351
switch (subcommand) {
331352
case t("logs.name"):

packages/bot/src/commands/context_menus.ts

Lines changed: 161 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import { cmdLn, t } from "@dicelette/localization";
2-
import type { Translation } from "@dicelette/types";
2+
import {
3+
DEFAULT_TEMPLATE,
4+
LinksVariables,
5+
type TemplateResult,
6+
type Translation,
7+
} from "@dicelette/types";
38
import type { EClient } from "client";
49
import * as Djs from "discord.js";
510
import { getLangAndConfig } from "../utils";
611

12+
type Results = {
13+
info: string;
14+
dice: string;
15+
};
16+
17+
type Variables = {
18+
rolls: Results[];
19+
stats?: string;
20+
link: string;
21+
};
22+
723
export const contextMenus = [
824
new Djs.ContextMenuCommandBuilder()
925
.setName(t("copyRollResult.name"))
@@ -16,16 +32,21 @@ export async function commandMenu(
1632
client: EClient
1733
) {
1834
const { ul } = getLangAndConfig(client, interaction);
35+
1936
if (interaction.targetMessage.author.id !== client.user?.id) {
2037
await interaction.reply({
2138
content: ul("copyRollResult.error.notBot"),
39+
flags: Djs.MessageFlags.Ephemeral,
2240
});
2341
return;
2442
}
43+
44+
const template = getTemplate(client, interaction);
45+
2546
const message = interaction.targetMessage.content;
2647
const messageUrl = interaction.targetMessage.url;
2748

28-
const link = await finalLink(message, messageUrl, ul);
49+
const link = finalLink(template, message, messageUrl);
2950
if (!link) {
3051
await interaction.reply({
3152
content: ul("copyRollResult.error.noResult"),
@@ -37,48 +58,158 @@ export async function commandMenu(
3758
content: `\`\`${link}\`\``,
3859
flags: Djs.MessageFlags.Ephemeral,
3960
});
61+
}
4062

41-
/**
42-
* Not needed anymore as mobile support copy in codeblocks
43-
await interaction.followUp({
44-
content: `${link}`,
45-
flags: Djs.MessageFlags.Ephemeral,
63+
function replaceAllTokens(text: string, map: Record<string, string>) {
64+
return Object.entries(map).reduce((acc, [k, v]) => acc.replaceAll(k, v), text);
65+
}
66+
67+
export function finalLink(
68+
template: TemplateResult | undefined,
69+
message: string,
70+
messageUrl: string
71+
) {
72+
const variables = getVariablesTemplate(message, messageUrl);
73+
if (!variables) return undefined;
74+
template = template ?? DEFAULT_TEMPLATE;
75+
76+
const resultsText = createResultFromTemplate(template, variables);
77+
78+
let finalText = replaceAllTokens(template!.final, {
79+
[LinksVariables.LINK]: variables.link,
80+
[LinksVariables.RESULTS]: resultsText,
81+
});
82+
83+
if (variables.stats) {
84+
const statText = getShortLong(variables.stats);
85+
const statFinal = replaceAllTokens(template!.format.name, {
86+
[LinksVariables.NAME]: statText.long,
87+
[LinksVariables.NAME_LONG]: statText.long,
88+
[LinksVariables.NAME_SHORT]: statText.short,
89+
});
90+
finalText = replaceAllTokens(finalText, {
91+
[LinksVariables.NAME]: statFinal,
92+
[LinksVariables.NAME_LONG]: statFinal,
93+
[LinksVariables.NAME_SHORT]: statFinal,
94+
});
95+
} else {
96+
finalText = replaceAllTokens(finalText, {
97+
[LinksVariables.NAME]: "",
98+
[LinksVariables.NAME_LONG]: "",
99+
[LinksVariables.NAME_SHORT]: "",
100+
});
101+
}
102+
103+
return finalText;
104+
}
105+
106+
function createResultFromTemplate(
107+
template: TemplateResult | undefined,
108+
variables: Variables
109+
) {
110+
template = template ?? DEFAULT_TEMPLATE;
111+
const fmt = template!.format;
112+
113+
const rollsText = variables.rolls.map(({ info, dice }) => {
114+
const infoTrim = info.trim();
115+
const diceTrim = dice.trim();
116+
117+
const infoFinal = infoTrim
118+
? (() => {
119+
const c = getShortLong(infoTrim);
120+
return replaceAllTokens(fmt.info, {
121+
[LinksVariables.INFO]: c.long,
122+
[LinksVariables.INFO_LONG]: c.long,
123+
[LinksVariables.INFO_SHORT]: c.short,
124+
}).trim();
125+
})()
126+
: "";
127+
128+
const resultFinal = diceTrim
129+
? fmt.dice.replace(LinksVariables.DICE, diceTrim).trim()
130+
: "";
131+
132+
return template!.results
133+
.replace(LinksVariables.INFO, infoFinal)
134+
.replaceAll(LinksVariables.INFO_LONG, infoFinal)
135+
.replaceAll(LinksVariables.INFO_SHORT, infoFinal)
136+
.replace(LinksVariables.DICE, resultFinal)
137+
.trim();
46138
});
47-
*/
139+
140+
return rollsText.join(template!.joinResult);
141+
}
142+
143+
function getShortLong(text: string) {
144+
text = text.replaceAll("**", "").trim();
145+
const short =
146+
text.split(" ").length > 1
147+
? text
148+
.split(" ")
149+
.map((word) => word.charAt(0).toUpperCase())
150+
.join("")
151+
: text;
152+
return {
153+
/** Complet */
154+
long: text,
155+
/** Initial only if word > 1, like Foo Bar = FB and Foo will be Foo*/
156+
short,
157+
};
48158
}
49159

50-
async function finalLink(message: string, messageUrl: string, ul: Translation) {
160+
function getVariablesTemplate(message: string, messageUrl: string) {
51161
const regexResultForRoll = /= `(?<result>.*)`/gi;
52-
const successFail = /( {2}|_ _ )(?<compare>.*) /gi;
162+
const successFail = /( {2}|_ _ )(?<info>.*) /gi;
53163

54164
const list = message.split("\n");
55-
const res: string[] = [];
165+
const variables: Results[] = [];
56166
for (const line of list) {
57167
if (!line.match(/^[\s_]+/)) continue;
58168
const match = regexResultForRoll.exec(line)?.groups?.result;
59-
const compare = successFail.exec(line)?.groups?.compare;
60-
if (match && compare) res.push(`${compare.trim()} — \`${match.trim()}\``);
61-
else if (match) res.push(`\`${match.trim()}\``);
169+
const info = successFail.exec(line)?.groups?.info;
170+
if (match && info) variables.push({ dice: match.trim(), info: info.trim() });
171+
else if (match) variables.push({ dice: match.trim(), info: "" });
62172
}
63-
64-
if (res.length === 0) return undefined;
173+
if (variables.length === 0) return undefined;
65174

66175
const statsReg = /\[__(?<stats>.*)__]/gi;
67176
const stats = statsReg.exec(message)?.groups?.stats;
68-
69177
const regexSavedDice =
70178
/-# (?<saved>https:\/\/discord\.com\/channels\/\d+\/\d+\/\d+)/gi;
71-
let savedDice = regexSavedDice.exec(message)?.groups?.saved;
72-
const generateMessage = `[[${stats ? `__${stats}__${ul("common.space")}: ` : ""}${res.join(" ; ")}]]`;
73-
if (!savedDice) savedDice = messageUrl;
74-
return `${generateMessage}(<${savedDice}>)`;
179+
const savedDice = regexSavedDice.exec(message)?.groups?.saved;
180+
181+
return {
182+
link: savedDice ?? messageUrl,
183+
rolls: variables,
184+
stats: stats ? stats : undefined,
185+
};
186+
}
187+
188+
function getTemplate(
189+
client: EClient,
190+
interaction: Djs.ButtonInteraction | Djs.MessageContextMenuCommandInteraction
191+
) {
192+
const templateExportText = client.settings.get(
193+
interaction.guildId!,
194+
"createLinkTemplate"
195+
);
196+
const userTemplate = client.userSettings.get(
197+
interaction.guildId!,
198+
interaction.user.id
199+
)?.createLinkTemplate;
200+
return templateExportText ?? userTemplate ?? DEFAULT_TEMPLATE;
75201
}
76202

77-
export async function mobileLink(interaction: Djs.ButtonInteraction, ul: Translation) {
203+
export async function mobileLink(
204+
interaction: Djs.ButtonInteraction,
205+
ul: Translation,
206+
client: EClient
207+
) {
78208
const message = interaction.message.content;
79209
const messageUrl = interaction.message.url;
210+
const template = getTemplate(client, interaction);
80211
//check user mobile or desktop
81-
const link = await finalLink(message, messageUrl, ul);
212+
const link = finalLink(template, message, messageUrl);
82213
if (!link) {
83214
await interaction.reply({
84215
content: ul("copyRollResult.error.noResult"),
@@ -92,10 +223,15 @@ export async function mobileLink(interaction: Djs.ButtonInteraction, ul: Transla
92223
});
93224
}
94225

95-
export async function desktopLink(interaction: Djs.ButtonInteraction, ul: Translation) {
226+
export async function desktopLink(
227+
interaction: Djs.ButtonInteraction,
228+
ul: Translation,
229+
client: EClient
230+
) {
231+
const template = getTemplate(client, interaction);
96232
const message = interaction.message.content;
97233
const messageUrl = interaction.message.url;
98-
const link = await finalLink(message, messageUrl, ul);
234+
const link = finalLink(template, message, messageUrl);
99235
if (!link) {
100236
await interaction.reply({
101237
content: ul("copyRollResult.error.noResult"),

packages/bot/src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { PRIVATES_COMMANDS } from "./private";
55
import { GLOBAL_CMD, ROLL_AUTO, ROLL_CMDLIST, ROLL_DB } from "./roll";
66
import { choose, GIMMICK, getCount, help } from "./tools";
77
import newScene from "./tools/new_scene";
8+
import { userSettings } from "./userSettings";
89

910
export const AUTOCOMPLETE_COMMANDS = [...ROLL_AUTO, ...GIMMICK, deleteChar, help];
1011
export const COMMANDS = [
@@ -18,6 +19,7 @@ export const COMMANDS = [
1819
getCount,
1920
...GLOBAL_CMD,
2021
choose,
22+
userSettings,
2123
];
2224

2325
export const DATABASE_COMMANDS = [...GIMMICK, ...ROLL_DB];
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { t } from "@dicelette/localization";
2+
import type { EClient } from "client";
3+
import * as Djs from "discord.js";
4+
import { getLangAndConfig } from "utils";
5+
import { createLinksCmdOptions, getTemplateValues, setTemplate } from "./setTemplate";
6+
7+
export const userSettings = {
8+
data: new Djs.SlashCommandBuilder()
9+
.setNames("userSettings.name")
10+
.setDescriptions("userSettings.description")
11+
.addSubcommandGroup((group) =>
12+
createLinksCmdOptions(
13+
group
14+
.setNames("userSettings.createLink.title")
15+
.setDescriptions("userSettings.createLink.description")
16+
)
17+
),
18+
execute: async (interaction: Djs.ChatInputCommandInteraction, client: EClient) => {
19+
const group = interaction.options.getSubcommandGroup(true);
20+
const subcommand = interaction.options.getSubcommand(true);
21+
const { ul } = getLangAndConfig(client, interaction);
22+
if (group === t("userSettings.createLink.title")) {
23+
if (subcommand === t("userSettings.createLink.format.name"))
24+
return await setTemplate(client, interaction);
25+
if (subcommand === t("userSettings.createLink.display.name"))
26+
return await getTemplateValues(client, ul, interaction);
27+
}
28+
},
29+
};
30+
31+
export { setTemplate, getTemplateValues, createLinksCmdOptions };

0 commit comments

Comments
 (0)