lazy-gravity 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +401 -41
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/commands/chatCommandHandler.js +43 -3
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +456 -27
- package/dist/events/messageCreateHandler.js +66 -7
- package/dist/handlers/__tests__/fileChangeButtonAction.test.js +85 -0
- package/dist/handlers/approvalButtonAction.js +2 -1
- package/dist/handlers/errorPopupButtonAction.js +2 -1
- package/dist/handlers/fileChangeButtonAction.js +57 -0
- package/dist/handlers/genericActionButtonAction.js +72 -0
- package/dist/handlers/planningButtonAction.js +126 -23
- package/dist/handlers/planningModalSubmitAction.js +38 -0
- package/dist/handlers/questionSelectAction.js +70 -0
- package/dist/handlers/questionSkipAction.js +68 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/platform/discord/wrappers.js +76 -0
- package/dist/services/approvalDetector.js +110 -35
- package/dist/services/artifactService.js +103 -37
- package/dist/services/assistantDomExtractor.js +194 -3
- package/dist/services/cdpBridgeManager.js +149 -8
- package/dist/services/cdpConnectionPool.js +22 -2
- package/dist/services/cdpService.js +134 -15
- package/dist/services/chatSessionService.js +147 -40
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +80 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +27 -1
- package/dist/services/questionDetector.js +428 -0
- package/dist/services/responseMonitor.js +45 -3
- package/dist/services/runCommandDetector.js +14 -7
- package/dist/ui/artifactsUi.js +4 -3
- package/dist/utils/consecutiveEmptyPollGate.js +21 -0
- package/dist/utils/fileOpenCache.js +29 -0
- package/dist/utils/htmlToDiscordMarkdown.js +5 -2
- package/dist/utils/pathUtils.js +12 -0
- package/dist/utils/projectResolver.js +10 -0
- package/dist/utils/questionActionUtils.js +47 -0
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createQuestionSelectAction = createQuestionSelectAction;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const notificationSender_1 = require("../services/notificationSender");
|
|
6
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
7
|
+
const projectResolver_1 = require("../utils/projectResolver");
|
|
8
|
+
function createQuestionSelectAction(deps) {
|
|
9
|
+
return {
|
|
10
|
+
match(customId) {
|
|
11
|
+
return customId.startsWith(notificationSender_1.QUESTION_SELECT_ACTION_PREFIX);
|
|
12
|
+
},
|
|
13
|
+
async execute(interaction, values) {
|
|
14
|
+
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SELECT_ACTION_PREFIX);
|
|
15
|
+
if (!parsed)
|
|
16
|
+
return false;
|
|
17
|
+
const selectedOption = parseInt(values[0], 10);
|
|
18
|
+
if (isNaN(selectedOption))
|
|
19
|
+
return false;
|
|
20
|
+
const channelId = parsed.channelId;
|
|
21
|
+
if (channelId && channelId !== interaction.channel.id) {
|
|
22
|
+
await interaction
|
|
23
|
+
.reply({ text: 'This question is linked to a different session channel.', ephemeral: true })
|
|
24
|
+
.catch(() => { });
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const projectName = (0, projectResolver_1.resolveProjectName)(deps, interaction.channel.id, parsed.projectName);
|
|
28
|
+
logger_1.logger.debug(`[QuestionSelectAction] project=${projectName ?? 'null'} channel=${interaction.channel.id}`);
|
|
29
|
+
const detector = projectName
|
|
30
|
+
? deps.bridge.pool.getQuestionDetector(projectName)
|
|
31
|
+
: undefined;
|
|
32
|
+
if (!detector) {
|
|
33
|
+
logger_1.logger.warn(`[QuestionSelectAction] No detector for project=${projectName}`);
|
|
34
|
+
await interaction
|
|
35
|
+
.reply({ text: 'Question detector not found.', ephemeral: true })
|
|
36
|
+
.catch(() => { });
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
await interaction.deferUpdate().catch(() => { });
|
|
40
|
+
let success = false;
|
|
41
|
+
try {
|
|
42
|
+
success = await detector.submitOption(selectedOption);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
46
|
+
logger_1.logger.error(`[QuestionSelectAction] CDP click failed: ${msg}`);
|
|
47
|
+
await interaction
|
|
48
|
+
.followUp({ text: `Failed to answer question: ${msg}`, ephemeral: true })
|
|
49
|
+
.catch(() => { });
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (success) {
|
|
53
|
+
const updatePayload = { text: `✅ Submitted option ${selectedOption + 1}.\n\n⏳ IDE is working on the response...`, components: [] };
|
|
54
|
+
try {
|
|
55
|
+
await interaction.editReply(updatePayload);
|
|
56
|
+
}
|
|
57
|
+
catch (editErr) {
|
|
58
|
+
logger_1.logger.warn('[QuestionSelectAction] editReply failed, sending followUp:', editErr);
|
|
59
|
+
await interaction.followUp({ text: `✅ Submitted option ${selectedOption + 1}.\n\n⏳ IDE is working on the response...` }).catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
await interaction
|
|
64
|
+
.followUp({ text: 'Failed to answer question (it may have been resolved already).', ephemeral: true })
|
|
65
|
+
.catch(() => { });
|
|
66
|
+
}
|
|
67
|
+
return success;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createQuestionSkipAction = createQuestionSkipAction;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const notificationSender_1 = require("../services/notificationSender");
|
|
6
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
7
|
+
const projectResolver_1 = require("../utils/projectResolver");
|
|
8
|
+
function createQuestionSkipAction(deps) {
|
|
9
|
+
return {
|
|
10
|
+
match(customId) {
|
|
11
|
+
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(customId, notificationSender_1.QUESTION_SKIP_ACTION_PREFIX);
|
|
12
|
+
return parsed ? { action: parsed.action } : null;
|
|
13
|
+
},
|
|
14
|
+
async execute(interaction, params) {
|
|
15
|
+
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SKIP_ACTION_PREFIX);
|
|
16
|
+
if (!parsed)
|
|
17
|
+
return false;
|
|
18
|
+
const channelId = parsed.channelId;
|
|
19
|
+
if (channelId && channelId !== interaction.channel.id) {
|
|
20
|
+
await interaction
|
|
21
|
+
.reply({ text: 'This question is linked to a different session channel.', ephemeral: true })
|
|
22
|
+
.catch(() => { });
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const projectName = (0, projectResolver_1.resolveProjectName)(deps, interaction.channel.id, parsed.projectName);
|
|
26
|
+
logger_1.logger.debug(`[QuestionSkipAction] project=${projectName ?? 'null'} channel=${interaction.channel.id}`);
|
|
27
|
+
const detector = projectName
|
|
28
|
+
? deps.bridge.pool.getQuestionDetector(projectName)
|
|
29
|
+
: undefined;
|
|
30
|
+
if (!detector) {
|
|
31
|
+
logger_1.logger.warn(`[QuestionSkipAction] No detector for project=${projectName}`);
|
|
32
|
+
await interaction
|
|
33
|
+
.reply({ text: 'Question detector not found.', ephemeral: true })
|
|
34
|
+
.catch(() => { });
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
await interaction.deferUpdate().catch(() => { });
|
|
38
|
+
let success = false;
|
|
39
|
+
try {
|
|
40
|
+
success = await detector.skipQuestion();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44
|
+
logger_1.logger.error(`[QuestionSkipAction] CDP click failed: ${msg}`);
|
|
45
|
+
await interaction
|
|
46
|
+
.followUp({ text: `Failed to skip question: ${msg}`, ephemeral: true })
|
|
47
|
+
.catch(() => { });
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (success) {
|
|
51
|
+
const updatePayload = { text: '✅ Skipped question.\n\n⏳ IDE is working on the response...', components: [] };
|
|
52
|
+
try {
|
|
53
|
+
await interaction.editReply(updatePayload);
|
|
54
|
+
}
|
|
55
|
+
catch (editErr) {
|
|
56
|
+
logger_1.logger.warn('[QuestionSkipAction] editReply failed, sending followUp:', editErr);
|
|
57
|
+
await interaction.followUp({ text: '✅ Skipped question.\n\n⏳ IDE is working on the response...' }).catch(() => { });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await interaction
|
|
62
|
+
.followUp({ text: 'Failed to skip question (it may have been resolved already).', ephemeral: true })
|
|
63
|
+
.catch(() => { });
|
|
64
|
+
}
|
|
65
|
+
return success;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createRunCommandButtonAction = createRunCommandButtonAction;
|
|
10
10
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
11
|
+
const projectResolver_1 = require("../utils/projectResolver");
|
|
11
12
|
const logger_1 = require("../utils/logger");
|
|
12
13
|
function createRunCommandButtonAction(deps) {
|
|
13
14
|
return {
|
|
@@ -31,7 +32,7 @@ function createRunCommandButtonAction(deps) {
|
|
|
31
32
|
.catch(() => { });
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
|
-
const projectName =
|
|
35
|
+
const projectName = (0, projectResolver_1.resolveProjectName)(deps, interaction.channel.id, params.projectName);
|
|
35
36
|
const detector = projectName
|
|
36
37
|
? deps.bridge.pool.getRunCommandDetector(projectName)
|
|
37
38
|
: undefined;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderDiscordResponse = renderDiscordResponse;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
/**
|
|
6
|
+
* Splits a long string into chunks that fit within Discord's embed description limit (4096 chars).
|
|
7
|
+
* Tries to split on double newlines, then single newlines, then spaces, before hard splitting.
|
|
8
|
+
*/
|
|
9
|
+
function chunkText(text, limit = 4000) {
|
|
10
|
+
if (!text)
|
|
11
|
+
return [];
|
|
12
|
+
if (text.length <= limit)
|
|
13
|
+
return [text];
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let remaining = text;
|
|
16
|
+
while (remaining.length > 0) {
|
|
17
|
+
if (remaining.length <= limit) {
|
|
18
|
+
chunks.push(remaining);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
let slice = remaining.slice(0, limit);
|
|
22
|
+
let splitIndex = slice.lastIndexOf('\n\n');
|
|
23
|
+
if (splitIndex === -1 || splitIndex < limit * 0.5) {
|
|
24
|
+
splitIndex = slice.lastIndexOf('\n');
|
|
25
|
+
}
|
|
26
|
+
if (splitIndex === -1 || splitIndex < limit * 0.5) {
|
|
27
|
+
splitIndex = slice.lastIndexOf(' ');
|
|
28
|
+
}
|
|
29
|
+
if (splitIndex === -1 || splitIndex < limit * 0.5) {
|
|
30
|
+
splitIndex = limit; // Hard split
|
|
31
|
+
}
|
|
32
|
+
chunks.push(remaining.slice(0, splitIndex).trim());
|
|
33
|
+
remaining = remaining.slice(splitIndex).trim();
|
|
34
|
+
}
|
|
35
|
+
return chunks;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders a classified Antigravity response into a Discord Message payload.
|
|
39
|
+
*
|
|
40
|
+
* Handles chunking for large outputs and structured cards (plan, files, actions).
|
|
41
|
+
*/
|
|
42
|
+
function renderDiscordResponse(result, options) {
|
|
43
|
+
const embeds = [];
|
|
44
|
+
const components = [];
|
|
45
|
+
// 1. Final Output Text
|
|
46
|
+
if (result.finalOutputText) {
|
|
47
|
+
const textChunks = chunkText(result.finalOutputText, 4000); // leave some buffer
|
|
48
|
+
for (let i = 0; i < textChunks.length && i < 8; i++) { // max 10 embeds total, leave room
|
|
49
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
50
|
+
.setColor(0x2b2d31) // Discord dark mode background-ish / neutral
|
|
51
|
+
.setDescription(textChunks[i]);
|
|
52
|
+
embeds.push(embed);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 2. Plan Cards
|
|
56
|
+
if (result.planCards && result.planCards.length > 0) {
|
|
57
|
+
const planText = result.planCards.join('\n\n');
|
|
58
|
+
const chunks = chunkText(planText, 4000);
|
|
59
|
+
for (let i = 0; i < chunks.length && embeds.length < 10; i++) {
|
|
60
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
61
|
+
.setTitle(i === 0 ? 'Plan' : null)
|
|
62
|
+
.setColor(0x5865f2) // Blurple
|
|
63
|
+
.setDescription(chunks[i]);
|
|
64
|
+
embeds.push(embed);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 3. File Changes
|
|
68
|
+
if (result.fileChanges && result.fileChanges.length > 0) {
|
|
69
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
70
|
+
.setTitle('File Changes')
|
|
71
|
+
.setColor(0x3ba55c); // Greenish
|
|
72
|
+
// Chunk into fields if necessary (max 25 fields, 1024 chars per field)
|
|
73
|
+
let currentFieldVal = '';
|
|
74
|
+
let fieldCount = 0;
|
|
75
|
+
let processedCount = 0;
|
|
76
|
+
let truncated = false;
|
|
77
|
+
for (const file of result.fileChanges) {
|
|
78
|
+
if (fieldCount >= 24) {
|
|
79
|
+
truncated = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
const line = `- \`${file.path}\` (${file.type})\n`;
|
|
83
|
+
if (line.length > 1000) {
|
|
84
|
+
if (currentFieldVal) {
|
|
85
|
+
embed.addFields({ name: fieldCount === 0 ? 'Files' : '...', value: currentFieldVal });
|
|
86
|
+
currentFieldVal = '';
|
|
87
|
+
fieldCount++;
|
|
88
|
+
}
|
|
89
|
+
const splitLine = chunkText(line, 1000);
|
|
90
|
+
for (const sl of splitLine) {
|
|
91
|
+
if (fieldCount >= 24) {
|
|
92
|
+
truncated = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
embed.addFields({ name: fieldCount === 0 ? 'Files' : '...', value: sl || '-' });
|
|
96
|
+
fieldCount++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (currentFieldVal.length + line.length > 1000) {
|
|
100
|
+
if (currentFieldVal) {
|
|
101
|
+
embed.addFields({ name: fieldCount === 0 ? 'Files' : '...', value: currentFieldVal });
|
|
102
|
+
fieldCount++;
|
|
103
|
+
}
|
|
104
|
+
currentFieldVal = line;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
currentFieldVal += line;
|
|
108
|
+
}
|
|
109
|
+
if (!truncated) {
|
|
110
|
+
processedCount++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (currentFieldVal && fieldCount < 24) {
|
|
114
|
+
embed.addFields({ name: fieldCount === 0 ? 'Files' : '...', value: currentFieldVal });
|
|
115
|
+
fieldCount++;
|
|
116
|
+
}
|
|
117
|
+
if (processedCount < result.fileChanges.length) {
|
|
118
|
+
const remaining = result.fileChanges.length - processedCount;
|
|
119
|
+
embed.addFields({ name: '...', value: `*...and ${remaining} more*` });
|
|
120
|
+
}
|
|
121
|
+
embeds.push(embed);
|
|
122
|
+
}
|
|
123
|
+
// 4. Action Buttons
|
|
124
|
+
if (result.actionButtons && result.actionButtons.length > 0) {
|
|
125
|
+
const row = new discord_js_1.ActionRowBuilder();
|
|
126
|
+
for (let i = 0; i < result.actionButtons.length && i < 5; i++) { // Max 5 buttons per row
|
|
127
|
+
const btnText = result.actionButtons[i];
|
|
128
|
+
let customId = `action_btn_${btnText.toLowerCase().replace(/\s+/g, '_')}`;
|
|
129
|
+
if (options?.projectName && options?.channelId) {
|
|
130
|
+
const overhead = customId.length + 2 + options.channelId.length;
|
|
131
|
+
const safeProject = options.projectName.length > (100 - overhead)
|
|
132
|
+
? options.projectName.substring(0, 100 - overhead)
|
|
133
|
+
: options.projectName;
|
|
134
|
+
customId = `${customId}:${safeProject}:${options.channelId}`;
|
|
135
|
+
}
|
|
136
|
+
const btn = new discord_js_1.ButtonBuilder()
|
|
137
|
+
.setCustomId(customId)
|
|
138
|
+
.setLabel(btnText)
|
|
139
|
+
.setStyle(btnText.toLowerCase() === 'proceed' || btnText.toLowerCase() === 'open'
|
|
140
|
+
? discord_js_1.ButtonStyle.Primary
|
|
141
|
+
: discord_js_1.ButtonStyle.Secondary);
|
|
142
|
+
row.addComponents(btn);
|
|
143
|
+
}
|
|
144
|
+
components.push(row);
|
|
145
|
+
}
|
|
146
|
+
// 5. Fallback if absolutely empty
|
|
147
|
+
if (embeds.length === 0) {
|
|
148
|
+
if (result.activityLines && result.activityLines.length > 0) {
|
|
149
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
150
|
+
.setColor(0x2b2d31)
|
|
151
|
+
.setDescription('*(Processing...)*\n' + result.activityLines.slice(-3).join('\n').slice(0, 3900));
|
|
152
|
+
embeds.push(embed);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
return { content: '*(No output)*' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Discord allows max 10 embeds per message.
|
|
159
|
+
const finalEmbeds = embeds.slice(0, 10);
|
|
160
|
+
return {
|
|
161
|
+
embeds: finalEmbeds,
|
|
162
|
+
components: components.length > 0 ? components : undefined,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -15,6 +15,7 @@ exports.wrapDiscordMessage = wrapDiscordMessage;
|
|
|
15
15
|
exports.wrapDiscordButton = wrapDiscordButton;
|
|
16
16
|
exports.wrapDiscordSelect = wrapDiscordSelect;
|
|
17
17
|
exports.wrapDiscordCommand = wrapDiscordCommand;
|
|
18
|
+
exports.wrapDiscordModalSubmit = wrapDiscordModalSubmit;
|
|
18
19
|
const discord_js_1 = require("discord.js");
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Style mapping
|
|
@@ -107,6 +108,32 @@ function toDiscordComponents(rows) {
|
|
|
107
108
|
return actionRow;
|
|
108
109
|
});
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Convert a platform ModalDef to a discord.js ModalBuilder.
|
|
113
|
+
*/
|
|
114
|
+
function toDiscordModal(modal) {
|
|
115
|
+
const builder = new discord_js_1.ModalBuilder()
|
|
116
|
+
.setCustomId(modal.customId)
|
|
117
|
+
.setTitle(modal.title);
|
|
118
|
+
for (const row of modal.components) {
|
|
119
|
+
const actionRow = new discord_js_1.ActionRowBuilder();
|
|
120
|
+
for (const comp of row.components) {
|
|
121
|
+
const input = new discord_js_1.TextInputBuilder()
|
|
122
|
+
.setCustomId(comp.customId)
|
|
123
|
+
.setLabel(comp.label)
|
|
124
|
+
.setStyle(comp.style === 'paragraph' ? discord_js_1.TextInputStyle.Paragraph : discord_js_1.TextInputStyle.Short);
|
|
125
|
+
if (comp.placeholder !== undefined) {
|
|
126
|
+
input.setPlaceholder(comp.placeholder);
|
|
127
|
+
}
|
|
128
|
+
if (comp.required !== undefined) {
|
|
129
|
+
input.setRequired(comp.required);
|
|
130
|
+
}
|
|
131
|
+
actionRow.addComponents(input);
|
|
132
|
+
}
|
|
133
|
+
builder.addComponents(actionRow);
|
|
134
|
+
}
|
|
135
|
+
return builder;
|
|
136
|
+
}
|
|
110
137
|
/**
|
|
111
138
|
* Convert a platform MessagePayload to discord.js message send/reply options.
|
|
112
139
|
*
|
|
@@ -256,6 +283,9 @@ function wrapDiscordButton(interaction) {
|
|
|
256
283
|
const sent = await interaction.followUp(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
257
284
|
return wrapDiscordSentMessage(sent);
|
|
258
285
|
},
|
|
286
|
+
async showModal(modal) {
|
|
287
|
+
await interaction.showModal(toDiscordModal(modal));
|
|
288
|
+
},
|
|
259
289
|
};
|
|
260
290
|
}
|
|
261
291
|
/** Wrap a discord.js StringSelectMenuInteraction as a PlatformSelectInteraction. */
|
|
@@ -329,3 +359,49 @@ function wrapDiscordCommand(interaction) {
|
|
|
329
359
|
},
|
|
330
360
|
};
|
|
331
361
|
}
|
|
362
|
+
/** Wrap a discord.js ModalSubmitInteraction as a PlatformModalSubmitInteraction. */
|
|
363
|
+
function wrapDiscordModalSubmit(interaction) {
|
|
364
|
+
const user = wrapDiscordUser(interaction.user);
|
|
365
|
+
const channel = interaction.channel
|
|
366
|
+
? wrapDiscordChannel(interaction.channel)
|
|
367
|
+
: buildFallbackChannel(interaction.channelId ?? 'unknown');
|
|
368
|
+
const fields = new Map();
|
|
369
|
+
for (const [key, field] of interaction.fields.fields.entries()) {
|
|
370
|
+
fields.set(key, interaction.fields.getTextInputValue(key));
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
id: interaction.id,
|
|
374
|
+
platform: 'discord',
|
|
375
|
+
customId: interaction.customId,
|
|
376
|
+
user,
|
|
377
|
+
channel,
|
|
378
|
+
messageId: interaction.message?.id ?? null,
|
|
379
|
+
fields,
|
|
380
|
+
async deferUpdate() {
|
|
381
|
+
if (interaction.isFromMessage()) {
|
|
382
|
+
await interaction.deferUpdate();
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
await interaction.deferReply({ ephemeral: true });
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
async reply(payload) {
|
|
389
|
+
await interaction.reply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
390
|
+
},
|
|
391
|
+
async update(payload) {
|
|
392
|
+
if (interaction.isFromMessage()) {
|
|
393
|
+
await interaction.update(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
await interaction.reply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async editReply(payload) {
|
|
400
|
+
await interaction.editReply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
401
|
+
},
|
|
402
|
+
async followUp(payload) {
|
|
403
|
+
const sent = await interaction.followUp(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
404
|
+
return wrapDiscordSentMessage(sent);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|