@helyx/module-reactionroles 1.0.14 → 1.0.17
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/CHANGELOG.md +26 -0
- package/README.md +37 -53
- package/dist/builder-interactions.d.ts +11 -0
- package/dist/builder-interactions.js +293 -0
- package/dist/builder-presentation.d.ts +30 -0
- package/dist/builder-presentation.js +343 -0
- package/dist/builder.d.ts +3 -0
- package/dist/builder.js +22 -16
- package/dist/cleanup-repository.d.ts +24 -0
- package/dist/cleanup-repository.js +118 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +4 -1
- package/dist/health.d.ts +16 -0
- package/dist/health.js +85 -0
- package/dist/index.js +66 -1147
- package/dist/logging.d.ts +33 -0
- package/dist/logging.js +40 -0
- package/dist/managed-resource.d.ts +2 -0
- package/dist/managed-resource.js +72 -0
- package/dist/member-interactions.d.ts +27 -0
- package/dist/member-interactions.js +360 -0
- package/dist/module-lifecycle.d.ts +3 -0
- package/dist/module-lifecycle.js +48 -0
- package/dist/panel-cache.d.ts +30 -0
- package/dist/panel-cache.js +197 -0
- package/dist/panel-create.d.ts +10 -0
- package/dist/panel-create.js +80 -0
- package/dist/panel-lifecycle.d.ts +47 -0
- package/dist/panel-lifecycle.js +272 -0
- package/dist/panel-managed.d.ts +14 -0
- package/dist/panel-managed.js +27 -0
- package/dist/panel-service.d.ts +13 -36
- package/dist/panel-service.js +48 -722
- package/dist/panel-support.d.ts +35 -0
- package/dist/panel-support.js +291 -0
- package/dist/permissions.d.ts +2 -0
- package/dist/permissions.js +8 -0
- package/dist/privacy.d.ts +5 -0
- package/dist/privacy.js +12 -0
- package/dist/record-mapping.d.ts +28 -0
- package/dist/record-mapping.js +161 -0
- package/dist/record-schema.d.ts +203 -0
- package/dist/record-schema.js +137 -0
- package/dist/records.d.ts +9 -201
- package/dist/records.js +32 -383
- package/dist/runtime-state.d.ts +9 -0
- package/dist/runtime-state.js +30 -0
- package/manifest.json +9 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @helyx/module-reactionroles
|
|
2
2
|
|
|
3
|
+
## 1.0.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [bbc4eee]
|
|
8
|
+
- @helyx/sdk@0.10.0
|
|
9
|
+
|
|
10
|
+
## 1.0.16
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Add declared command-operation permission targets, project them through both permission-management surfaces, and harden module lifecycle, health, visibility, bounded-work, optional-side-effect, command-permission, and Forms durable-workflow behavior. Preserve existing FAQ, Access Control, Achievements, Giveaways and Levels overrides through additive migrations while assigning every slash-command path its own permission identity.
|
|
15
|
+
|
|
16
|
+
## 1.0.15
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Add manifest-backed deployment support and credential-risk policy, enforce it
|
|
21
|
+
through hosted and self-host composition, and carry it into generated build
|
|
22
|
+
manifests and self-host module labels.
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- Updated dependencies
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @helyx/sdk@0.9.0
|
|
27
|
+
- @helyx/module-manifest@0.8.0
|
|
28
|
+
|
|
3
29
|
## 1.0.14
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,53 +1,37 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
Reaction Roles
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Reaction
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Helyx can remove the
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
guarded migration through the normal Helyx migration runner. Confirm module
|
|
39
|
-
health is healthy, create one panel of each control type through both the
|
|
40
|
-
dashboard and Discord, restart the shard worker, and verify the panels still
|
|
41
|
-
respond.
|
|
42
|
-
|
|
43
|
-
If a role, channel, emoji or owned message is removed, the panel should become
|
|
44
|
-
`needs_attention` or `message_missing`. Correct the Discord resource and use
|
|
45
|
-
the module's Repair or Repost action. A `cleanup_pending` panel remains safe to
|
|
46
|
-
use while Helyx retries deletion of the old owned message. Repeated or
|
|
47
|
-
abandoned cleanup failures are an operator alert and must be investigated
|
|
48
|
-
before promotion.
|
|
49
|
-
|
|
50
|
-
Disabling the module stops its interactions without deleting configuration.
|
|
51
|
-
Re-enable only after current channel permissions, role hierarchy and emoji
|
|
52
|
-
availability have been checked. Rollback may restore compatible application
|
|
53
|
-
code, but must not reverse the migration or delete panel data.
|
|
1
|
+
# @helyx/module-reactionroles
|
|
2
|
+
|
|
3
|
+
Reaction Roles publishes Helyx-owned panels that let members assign approved Discord roles to themselves.
|
|
4
|
+
|
|
5
|
+
## Availability and deployment
|
|
6
|
+
|
|
7
|
+
Reaction Roles is a **Live** module for Helyx-hosted and self-hosted deployments. Its migrations are additive and must be applied through the normal Helyx migration runner before the module starts.
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
Server managers create button, dropdown, or emoji-reaction panels through the dashboard. Panels support multiple-role and one-role-only selection, text or embed messages, persistent reactions by default, generated emoji-to-role keys, repair, repost, duplicate, and deletion workflows. Discord remains the source of truth for membership.
|
|
12
|
+
|
|
13
|
+
## Commands and interactions
|
|
14
|
+
|
|
15
|
+
The module has no slash-command root. Managers use the dashboard resource manager. Members use the published buttons, dropdowns, or reactions. The stable manager policy identifiers are `reactionroles.create`, `reactionroles.manage`, and `reactionroles.delete`.
|
|
16
|
+
|
|
17
|
+
## Dashboard configuration
|
|
18
|
+
|
|
19
|
+
The `panels` resource supports list, read, preview, create, update, duplicate, repair, repost, and delete operations. Publication validates channel access, role hierarchy, dangerous role permissions, custom emoji availability, entry limits, and optimistic revisions before changing Discord or stored state.
|
|
20
|
+
|
|
21
|
+
## Discord requirements
|
|
22
|
+
|
|
23
|
+
Helyx needs View Channel, Send Messages, and Manage Roles. Embed panels also need Embed Links. Reaction panels need Read Message History and Add Reactions. Non-persistent reaction panels need Manage Messages so Helyx can remove the member reaction after assigning the role. The module does not request Administrator or Message Content.
|
|
24
|
+
|
|
25
|
+
## Data, privacy and retention
|
|
26
|
+
|
|
27
|
+
Helyx stores panel and entry configuration, Helyx-owned message references, lifecycle state, revision, cleanup work, and privacy-detachable creator and updater IDs. It does not store member selections, role membership history, click history, or reaction history. Privacy access and erasure remain available while the module is disabled.
|
|
28
|
+
|
|
29
|
+
## Operations and failure behavior
|
|
30
|
+
|
|
31
|
+
Every owned-message cleanup record atomically schedules a bounded one-shot retry through the core scheduler; startup also reconciles paginated pending work before warming the bounded active-panel cache. Exhausted cleanup remains durable for operator review. Unknown message reactions use a short negative cache and coalesced lookups. Deleted or invalid Discord resources move affected panels to `message_missing` or `needs_attention`; repair or repost restores them. Health is unhealthy while stopped, when required services are missing, or after a recent primary operation failure, and degraded for cold-cache startup, cache saturation, or pending cleanup failure.
|
|
32
|
+
|
|
33
|
+
Disablement stops new interactions without deleting configuration. Re-enable only after checking channel access, role hierarchy, and emoji availability. Rollback may restore compatible application code, but must not reverse migrations or delete panel data.
|
|
34
|
+
|
|
35
|
+
## Documentation and support
|
|
36
|
+
|
|
37
|
+
User guides, permissions, troubleshooting, privacy details, and dashboard walkthroughs are published in the [Reaction Roles documentation](https://docs.helyx.gg/modules/reaction-roles/). Report defects through the [Helyx issue tracker](https://github.com/ZyC0R3/Helyx/issues).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ButtonContribution, ModalInteractionContext, SelectInteractionContext } from "@helyx/sdk";
|
|
2
|
+
export declare function continueDraft(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
3
|
+
export declare function replaceDraft(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
4
|
+
export declare function keepDraft(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
5
|
+
export declare function saveBasicDraft(context: ModalInteractionContext): Promise<void>;
|
|
6
|
+
export declare function openEntryModal(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
7
|
+
export declare function openEditEntryModal(context: SelectInteractionContext): Promise<void>;
|
|
8
|
+
export declare function saveEntry(context: ModalInteractionContext): Promise<void>;
|
|
9
|
+
export declare function publishDraft(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
10
|
+
export declare function cancelDraft(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
11
|
+
export declare function confirmDelete(context: Parameters<ButtonContribution["execute"]>[0]): Promise<void>;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { PANEL_LIMITS, PANEL_PURPOSE } from "./constants.js";
|
|
3
|
+
import { validatePanelDraft, } from "./builder.js";
|
|
4
|
+
import { reactionRolePanels as panels } from "./runtime-state.js";
|
|
5
|
+
import { basicModal, builderResponse, defaultsFromSessionState, entryModal, expiresAt, interactionMutationContext, managedObject, parseSessionDraft, replyExpired, requireGuild, serializeDraft, sessionService, } from "./builder-presentation.js";
|
|
6
|
+
function requiredStyle(values) {
|
|
7
|
+
const value = values[0];
|
|
8
|
+
if (value !== "text" && value !== "embed")
|
|
9
|
+
throw new Error("Choose Text or Embed");
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function requiredControl(values) {
|
|
13
|
+
const value = values[0];
|
|
14
|
+
if (value !== "buttons" && value !== "dropdown" && value !== "reactions")
|
|
15
|
+
throw new Error("Choose valid role controls");
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
export async function continueDraft(context) {
|
|
19
|
+
const guildId = requireGuild(context);
|
|
20
|
+
const session = await sessionService(context.services).getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
21
|
+
if (!session)
|
|
22
|
+
return replyExpired(context);
|
|
23
|
+
const draft = parseSessionDraft(session.state.draft);
|
|
24
|
+
if (draft) {
|
|
25
|
+
await context.reply(builderResponse(draft, context.services));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await context.showModal(basicModal(defaultsFromSessionState(session.state)));
|
|
29
|
+
}
|
|
30
|
+
export async function replaceDraft(context) {
|
|
31
|
+
const guildId = requireGuild(context);
|
|
32
|
+
const sessions = sessionService(context.services);
|
|
33
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
34
|
+
if (!session)
|
|
35
|
+
return replyExpired(context);
|
|
36
|
+
const replacement = managedObject(session.state.pendingReplacement)
|
|
37
|
+
? session.state.pendingReplacement
|
|
38
|
+
: {
|
|
39
|
+
mode: "create",
|
|
40
|
+
selectionMode: "multiple",
|
|
41
|
+
persistentReactions: true,
|
|
42
|
+
};
|
|
43
|
+
await sessions.create({
|
|
44
|
+
guildId,
|
|
45
|
+
userId: context.userId,
|
|
46
|
+
purpose: PANEL_PURPOSE,
|
|
47
|
+
state: { ...replacement },
|
|
48
|
+
expiresAt: expiresAt(),
|
|
49
|
+
replace: true,
|
|
50
|
+
});
|
|
51
|
+
await context.showModal(basicModal(defaultsFromSessionState(replacement)));
|
|
52
|
+
}
|
|
53
|
+
export async function keepDraft(context) {
|
|
54
|
+
const guildId = requireGuild(context);
|
|
55
|
+
const sessions = sessionService(context.services);
|
|
56
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
57
|
+
if (!session)
|
|
58
|
+
return replyExpired(context);
|
|
59
|
+
const state = { ...session.state };
|
|
60
|
+
delete state.pendingReplacement;
|
|
61
|
+
await sessions.update({
|
|
62
|
+
sessionId: session.sessionId,
|
|
63
|
+
state,
|
|
64
|
+
expectedVersion: session.version,
|
|
65
|
+
expiresAt: expiresAt(),
|
|
66
|
+
});
|
|
67
|
+
await context.reply({
|
|
68
|
+
ephemeral: true,
|
|
69
|
+
componentsV2: {
|
|
70
|
+
text: [{ content: "Your existing Reaction Roles draft was kept." }],
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export async function saveBasicDraft(context) {
|
|
75
|
+
const guildId = requireGuild(context);
|
|
76
|
+
const sessions = sessionService(context.services);
|
|
77
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
78
|
+
if (!session)
|
|
79
|
+
return replyExpired(context);
|
|
80
|
+
const prior = parseSessionDraft(session.state.draft);
|
|
81
|
+
const controlType = requiredControl(context.getStringSelectValues("control"));
|
|
82
|
+
const selectionMode = session.state.selectionMode === "one_only"
|
|
83
|
+
? "one_only"
|
|
84
|
+
: (prior?.selectionMode ?? "multiple");
|
|
85
|
+
const persistentReactions = controlType === "reactions" && selectionMode === "one_only"
|
|
86
|
+
? false
|
|
87
|
+
: (prior?.persistentReactions ??
|
|
88
|
+
session.state.persistentReactions !== false);
|
|
89
|
+
const draft = {
|
|
90
|
+
channelId: context.getSelectedChannelIds("channel")[0] ?? "",
|
|
91
|
+
title: context.getField("title"),
|
|
92
|
+
messageBody: context.getField("message"),
|
|
93
|
+
messageStyle: requiredStyle(context.getStringSelectValues("style")),
|
|
94
|
+
controlType,
|
|
95
|
+
selectionMode,
|
|
96
|
+
persistentReactions,
|
|
97
|
+
...(prior?.embedColour === undefined
|
|
98
|
+
? { embedColour: 0x7c3aed }
|
|
99
|
+
: { embedColour: prior.embedColour }),
|
|
100
|
+
...(prior?.dropdownPlaceholder
|
|
101
|
+
? { dropdownPlaceholder: prior.dropdownPlaceholder }
|
|
102
|
+
: {}),
|
|
103
|
+
entries: prior?.entries ?? [],
|
|
104
|
+
};
|
|
105
|
+
await sessions.update({
|
|
106
|
+
sessionId: session.sessionId,
|
|
107
|
+
state: { ...session.state, draft: serializeDraft(draft) },
|
|
108
|
+
expectedVersion: session.version,
|
|
109
|
+
expiresAt: expiresAt(),
|
|
110
|
+
});
|
|
111
|
+
await context.reply(builderResponse(draft, context.services));
|
|
112
|
+
}
|
|
113
|
+
export async function openEntryModal(context) {
|
|
114
|
+
const guildId = requireGuild(context);
|
|
115
|
+
const session = await sessionService(context.services).getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
116
|
+
if (!session)
|
|
117
|
+
return replyExpired(context);
|
|
118
|
+
const draft = parseSessionDraft(session.state.draft);
|
|
119
|
+
if (!draft)
|
|
120
|
+
return replyExpired(context);
|
|
121
|
+
if (draft.entries.length >= PANEL_LIMITS[draft.controlType]) {
|
|
122
|
+
await context.reply({
|
|
123
|
+
ephemeral: true,
|
|
124
|
+
componentsV2: {
|
|
125
|
+
text: [{ content: "This panel already has the maximum role entries." }],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await context.showModal(entryModal());
|
|
131
|
+
}
|
|
132
|
+
export async function openEditEntryModal(context) {
|
|
133
|
+
const guildId = requireGuild(context);
|
|
134
|
+
const sessions = sessionService(context.services);
|
|
135
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
136
|
+
if (!session)
|
|
137
|
+
return replyExpired(context);
|
|
138
|
+
const draft = parseSessionDraft(session.state.draft);
|
|
139
|
+
const entryId = context.selectedValues[0];
|
|
140
|
+
const entry = draft?.entries.find((candidate) => candidate.entryId === entryId);
|
|
141
|
+
if (!draft || !entry || context.selectedValues.length !== 1)
|
|
142
|
+
return replyExpired(context);
|
|
143
|
+
await sessions.update({
|
|
144
|
+
sessionId: session.sessionId,
|
|
145
|
+
state: { ...session.state, editingEntryId: entry.entryId },
|
|
146
|
+
expectedVersion: session.version,
|
|
147
|
+
expiresAt: expiresAt(),
|
|
148
|
+
});
|
|
149
|
+
await context.showModal(entryModal(entry, true));
|
|
150
|
+
}
|
|
151
|
+
export async function saveEntry(context) {
|
|
152
|
+
const guildId = requireGuild(context);
|
|
153
|
+
const sessions = sessionService(context.services);
|
|
154
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
155
|
+
if (!session)
|
|
156
|
+
return replyExpired(context);
|
|
157
|
+
const draft = parseSessionDraft(session.state.draft);
|
|
158
|
+
if (!draft)
|
|
159
|
+
return replyExpired(context);
|
|
160
|
+
const editingEntryId = typeof session.state.editingEntryId === "string"
|
|
161
|
+
? session.state.editingEntryId
|
|
162
|
+
: null;
|
|
163
|
+
const editingIndex = editingEntryId
|
|
164
|
+
? draft.entries.findIndex(({ entryId }) => entryId === editingEntryId)
|
|
165
|
+
: -1;
|
|
166
|
+
if (editingEntryId && editingIndex < 0)
|
|
167
|
+
return replyExpired(context);
|
|
168
|
+
const action = editingEntryId
|
|
169
|
+
? (context.getStringSelectValues("entry-action")[0] ?? "save")
|
|
170
|
+
: "save";
|
|
171
|
+
const entries = [...draft.entries];
|
|
172
|
+
if (editingEntryId && action === "remove") {
|
|
173
|
+
entries.splice(editingIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
else if (editingEntryId && action === "move-up") {
|
|
176
|
+
if (editingIndex > 0)
|
|
177
|
+
[entries[editingIndex - 1], entries[editingIndex]] = [
|
|
178
|
+
entries[editingIndex],
|
|
179
|
+
entries[editingIndex - 1],
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
else if (editingEntryId && action === "move-down") {
|
|
183
|
+
if (editingIndex < entries.length - 1)
|
|
184
|
+
[entries[editingIndex], entries[editingIndex + 1]] = [
|
|
185
|
+
entries[editingIndex + 1],
|
|
186
|
+
entries[editingIndex],
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const roleId = context.getSelectedRoleIds("role")[0] ?? "";
|
|
191
|
+
const buttonStyle = context.getStringSelectValues("button-style")[0];
|
|
192
|
+
const entry = {
|
|
193
|
+
entryId: editingEntryId ?? `entry-${randomUUID().replaceAll("-", "")}`,
|
|
194
|
+
roleId,
|
|
195
|
+
nameplate: context.getField("nameplate"),
|
|
196
|
+
...(context.getField("emoji").trim()
|
|
197
|
+
? { emoji: context.getField("emoji") }
|
|
198
|
+
: {}),
|
|
199
|
+
...(buttonStyle
|
|
200
|
+
? {
|
|
201
|
+
buttonStyle: buttonStyle,
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
};
|
|
205
|
+
if (editingEntryId)
|
|
206
|
+
entries[editingIndex] = entry;
|
|
207
|
+
else
|
|
208
|
+
entries.push(entry);
|
|
209
|
+
}
|
|
210
|
+
const next = entries.length === 0
|
|
211
|
+
? { ...draft, entries }
|
|
212
|
+
: validatePanelDraft({
|
|
213
|
+
...draft,
|
|
214
|
+
entries,
|
|
215
|
+
});
|
|
216
|
+
const state = {
|
|
217
|
+
...session.state,
|
|
218
|
+
draft: serializeDraft(next),
|
|
219
|
+
};
|
|
220
|
+
delete state.editingEntryId;
|
|
221
|
+
await sessions.update({
|
|
222
|
+
sessionId: session.sessionId,
|
|
223
|
+
state,
|
|
224
|
+
expectedVersion: session.version,
|
|
225
|
+
expiresAt: expiresAt(),
|
|
226
|
+
});
|
|
227
|
+
await context.reply(builderResponse(next, context.services));
|
|
228
|
+
}
|
|
229
|
+
export async function publishDraft(context) {
|
|
230
|
+
const guildId = requireGuild(context);
|
|
231
|
+
const sessions = sessionService(context.services);
|
|
232
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
233
|
+
if (!session)
|
|
234
|
+
return replyExpired(context);
|
|
235
|
+
const draft = parseSessionDraft(session.state.draft);
|
|
236
|
+
if (!draft)
|
|
237
|
+
return replyExpired(context);
|
|
238
|
+
const mutation = interactionMutationContext(context, guildId);
|
|
239
|
+
const panel = session.state.mode === "edit" &&
|
|
240
|
+
typeof session.state.panelId === "string" &&
|
|
241
|
+
typeof session.state.expectedRevision === "number"
|
|
242
|
+
? await panels.update(mutation, {
|
|
243
|
+
resourceId: session.state.panelId,
|
|
244
|
+
expectedRevision: session.state.expectedRevision,
|
|
245
|
+
draft,
|
|
246
|
+
})
|
|
247
|
+
: await panels.create(mutation, draft);
|
|
248
|
+
await sessions.delete(session.sessionId);
|
|
249
|
+
await context.reply({
|
|
250
|
+
ephemeral: true,
|
|
251
|
+
componentsV2: {
|
|
252
|
+
text: [
|
|
253
|
+
{
|
|
254
|
+
content: `Reaction Roles panel **${panel.title}** is active in <#${panel.channelId}>.`,
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
allowedUserMentionIds: [],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
export async function cancelDraft(context) {
|
|
262
|
+
const guildId = requireGuild(context);
|
|
263
|
+
const sessions = sessionService(context.services);
|
|
264
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
265
|
+
if (session)
|
|
266
|
+
await sessions.delete(session.sessionId);
|
|
267
|
+
await context.reply({
|
|
268
|
+
ephemeral: true,
|
|
269
|
+
componentsV2: { text: [{ content: "Nothing was changed." }] },
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
export async function confirmDelete(context) {
|
|
273
|
+
const guildId = requireGuild(context);
|
|
274
|
+
const sessions = sessionService(context.services);
|
|
275
|
+
const session = await sessions.getFor(guildId, context.userId, PANEL_PURPOSE);
|
|
276
|
+
if (!session ||
|
|
277
|
+
session.state.mode !== "delete" ||
|
|
278
|
+
typeof session.state.panelId !== "string" ||
|
|
279
|
+
typeof session.state.expectedRevision !== "number")
|
|
280
|
+
return replyExpired(context);
|
|
281
|
+
await panels.delete(interactionMutationContext(context, guildId), session.state.panelId, session.state.expectedRevision);
|
|
282
|
+
await sessions.delete(session.sessionId);
|
|
283
|
+
await context.reply({
|
|
284
|
+
ephemeral: true,
|
|
285
|
+
componentsV2: {
|
|
286
|
+
text: [
|
|
287
|
+
{
|
|
288
|
+
content: "The role panel was deleted. Existing member roles were not changed.",
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { InteractionSessionService, MessageResponse, ModalDefinition, ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { type PanelDraft } from "./builder.js";
|
|
3
|
+
import type { PanelMutationContext } from "./panel-service.js";
|
|
4
|
+
export declare function basicModal(panel?: Partial<PanelDraft>, editing?: boolean): ModalDefinition;
|
|
5
|
+
export declare function defaultsFromSessionState(state: Readonly<Record<string, unknown>>): Partial<PanelDraft>;
|
|
6
|
+
export declare function managedObject(value: unknown): value is Record<string, unknown>;
|
|
7
|
+
export declare function entryModal(entry?: PanelDraft["entries"][number], editing?: boolean): ModalDefinition;
|
|
8
|
+
export declare function builderResponse(draft: PanelDraft, services: ServiceAccess): MessageResponse;
|
|
9
|
+
export declare function interactionMutationContext(context: {
|
|
10
|
+
userId: string;
|
|
11
|
+
services: ServiceAccess;
|
|
12
|
+
interactionId: string;
|
|
13
|
+
sendToChannel: PanelMutationContext["sendToChannel"];
|
|
14
|
+
deleteChannelMessage: PanelMutationContext["deleteChannelMessage"];
|
|
15
|
+
editChannelMessage: PanelMutationContext["editChannelMessage"];
|
|
16
|
+
addChannelMessageReaction: PanelMutationContext["addChannelMessageReaction"];
|
|
17
|
+
}, guildId: string): PanelMutationContext;
|
|
18
|
+
export declare function sessionService(services: ServiceAccess): InteractionSessionService;
|
|
19
|
+
export declare function requireGuild(context: {
|
|
20
|
+
serverId?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function expiresAt(): Date;
|
|
23
|
+
export declare function serializeDraft(draft: PanelDraft): Record<string, unknown>;
|
|
24
|
+
export declare function parseSessionDraft(value: unknown): PanelDraft | null;
|
|
25
|
+
export declare function replyExpired(context: {
|
|
26
|
+
reply(response: MessageResponse): Promise<void>;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
export declare function stalePanelReply(context: {
|
|
29
|
+
reply(response: MessageResponse): Promise<void>;
|
|
30
|
+
}): Promise<void>;
|