Skip to content

Commit c196cdb

Browse files
author
Joshua Chittick
committed
refactor: centralize slack interaction payload parsing
1 parent 42ce92d commit c196cdb

File tree

1 file changed

+115
-34
lines changed

1 file changed

+115
-34
lines changed

packages/ims/slack/commands.ts

Lines changed: 115 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,35 @@ type AgentProvider = "opencode" | "claudecode" | "codex" | "kimi" | "kiro" | "ki
5858
type StatusMessageFormat = "aggressive" | "medium" | "minimum";
5959
type GitStrategy = "default" | "worktree";
6060

61+
type SlackActionBody = {
62+
actions?: Array<{
63+
value?: string;
64+
selected_option?: {
65+
value?: string;
66+
};
67+
}>;
68+
channel?: {
69+
id?: string;
70+
};
71+
user?: {
72+
id?: string;
73+
};
74+
trigger_id?: string;
75+
view?: {
76+
private_metadata?: string;
77+
id?: string;
78+
hash?: string;
79+
state?: {
80+
values?: Record<string, Record<string, { value?: string; selected_option?: { value?: string } }>>;
81+
};
82+
};
83+
message?: {
84+
thread_ts?: string;
85+
ts?: string;
86+
text?: string;
87+
};
88+
};
89+
6190
const AGENT_PROVIDERS: AgentProvider[] = ["opencode", "claudecode", "codex", "kimi", "kiro", "kilo", "qwen", "goose"];
6291

6392
const AGENT_PROVIDER_LABELS: Record<AgentProvider, string> = {
@@ -109,6 +138,38 @@ function toSelectableProvider(provider: "opencode" | "claudecode" | "codex" | "k
109138
return parseAgentProvider(provider);
110139
}
111140

141+
function getActionChannelId(body: SlackActionBody): string | undefined {
142+
return body.actions?.[0]?.value ?? body.channel?.id;
143+
}
144+
145+
function getActionUserId(body: SlackActionBody): string | undefined {
146+
return body.user?.id;
147+
}
148+
149+
function getActionTriggerId(body: SlackActionBody): string | undefined {
150+
return body.trigger_id;
151+
}
152+
153+
function getActionSelectedOptionValue(body: SlackActionBody): string | undefined {
154+
return body.actions?.[0]?.selected_option?.value;
155+
}
156+
157+
function getActionViewMetadata(body: SlackActionBody): string | undefined {
158+
return body.view?.private_metadata;
159+
}
160+
161+
function getActionThreadTs(body: SlackActionBody): string | undefined {
162+
return body.message?.thread_ts || body.message?.ts;
163+
}
164+
165+
function getActionMessageTs(body: SlackActionBody): string | undefined {
166+
return body.message?.ts;
167+
}
168+
169+
function getActionMessageText(body: SlackActionBody): string {
170+
return body.message?.text || "Question";
171+
}
172+
112173
function buildSettingsModal(params: {
113174
channelId: string;
114175
enabledProviders: AgentProvider[];
@@ -417,9 +478,11 @@ export function setupInteractiveHandlers(): void {
417478
slackApp.action(SETTINGS_LAUNCH_ACTION, async ({ ack, body, client }) => {
418479
await ack();
419480

420-
const channelId = (body as any).actions?.[0]?.value
421-
?? (body as any).channel?.id;
422-
if (!channelId) return;
481+
const actionBody = body as SlackActionBody;
482+
483+
const channelId = getActionChannelId(actionBody);
484+
const triggerId = getActionTriggerId(actionBody);
485+
if (!channelId || !triggerId) return;
423486

424487
try {
425488
await startOpenCodeServer();
@@ -448,18 +511,20 @@ export function setupInteractiveHandlers(): void {
448511
});
449512

450513
await client.views.open({
451-
trigger_id: (body as any).trigger_id,
514+
trigger_id: triggerId,
452515
view,
453516
});
454517
});
455518

456519
slackApp.action(GITHUB_LAUNCH_ACTION, async ({ ack, body, client }) => {
457520
await ack();
458521

459-
const channelId = (body as any).actions?.[0]?.value
460-
?? (body as any).channel?.id;
461-
const userId = (body as any).user?.id;
462-
if (!channelId || !userId) return;
522+
const actionBody = body as SlackActionBody;
523+
524+
const channelId = getActionChannelId(actionBody);
525+
const userId = getActionUserId(actionBody);
526+
const triggerId = getActionTriggerId(actionBody);
527+
if (!channelId || !userId || !triggerId) return;
463528

464529
const info = getGitHubInfoForUser(userId);
465530
const view = buildGitHubTokenModal({
@@ -471,16 +536,19 @@ export function setupInteractiveHandlers(): void {
471536
});
472537

473538
await client.views.open({
474-
trigger_id: (body as any).trigger_id,
539+
trigger_id: triggerId,
475540
view,
476541
});
477542
});
478543

479544
slackApp.action(GENERAL_SETTINGS_LAUNCH_ACTION, async ({ ack, body, client }) => {
480545
await ack();
481546

482-
const channelId = (body as any).actions?.[0]?.value
483-
?? (body as any).channel?.id
547+
const actionBody = body as SlackActionBody;
548+
const triggerId = getActionTriggerId(actionBody);
549+
if (!triggerId) return;
550+
551+
const channelId = getActionChannelId(actionBody)
484552
?? "";
485553
const generalSettings = getUserGeneralSettings();
486554
const view = buildGeneralSettingsModal({
@@ -490,18 +558,20 @@ export function setupInteractiveHandlers(): void {
490558
});
491559

492560
await client.views.open({
493-
trigger_id: (body as any).trigger_id,
561+
trigger_id: triggerId,
494562
view,
495563
});
496564
});
497565

498566
slackApp.action(GENERAL_SYNC_WORKSPACE_ACTION, async ({ ack, body, client }) => {
499567
await ack();
500568

501-
const channelId = (body as any).actions?.[0]?.value
502-
?? (body as any).view?.private_metadata
503-
?? (body as any).channel?.id;
504-
const userId = (body as any).user?.id;
569+
const actionBody = body as SlackActionBody;
570+
571+
const channelId = getActionChannelId(actionBody)
572+
?? getActionViewMetadata(actionBody)
573+
?? actionBody.channel?.id;
574+
const userId = getActionUserId(actionBody);
505575
if (!channelId || !userId) return;
506576

507577
const workspaces = getWorkspaces();
@@ -532,11 +602,14 @@ export function setupInteractiveHandlers(): void {
532602
slackApp.action(PROVIDER_ACTION, async ({ ack, body, client }) => {
533603
await ack();
534604

535-
const view = (body as any).view;
536-
if (!view) return;
605+
const actionBody = body as SlackActionBody;
606+
607+
const view = actionBody.view;
608+
if (!view || !view.id || !view.hash) return;
537609

538610
const channelId = view.private_metadata;
539-
const selectedOption = (body as any).actions?.[0]?.selected_option?.value;
611+
if (!channelId) return;
612+
const selectedOption = getActionSelectedOptionValue(actionBody);
540613
const selectedProvider = parseAgentProvider(selectedOption);
541614
if (selectedProvider === "opencode") {
542615
try {
@@ -656,7 +729,8 @@ export function setupInteractiveHandlers(): void {
656729
setChannelBaseBranch(channelId, baseBranch);
657730
setChannelSystemMessage(channelId, channelSystemMessage);
658731
} catch (err) {
659-
const userId = (body as any).user?.id;
732+
const actionBody = body as SlackActionBody;
733+
const userId = getActionUserId(actionBody);
660734
if (userId) {
661735
await client.chat.postEphemeral({
662736
channel: channelId,
@@ -667,7 +741,8 @@ export function setupInteractiveHandlers(): void {
667741
return;
668742
}
669743

670-
const userId = (body as any).user?.id;
744+
const actionBody = body as SlackActionBody;
745+
const userId = getActionUserId(actionBody);
671746
if (userId) {
672747
await client.chat.postEphemeral({
673748
channel: channelId,
@@ -686,9 +761,11 @@ export function setupInteractiveHandlers(): void {
686761

687762
await ack();
688763

689-
const userId = (body as any).user?.id;
690-
const channelId = (body as any).view?.private_metadata
691-
?? (body as any).channel?.id;
764+
const actionBody = body as SlackActionBody;
765+
766+
const userId = getActionUserId(actionBody);
767+
const channelId = getActionViewMetadata(actionBody)
768+
?? actionBody.channel?.id;
692769
if (!userId || !channelId) return;
693770

694771
try {
@@ -734,8 +811,9 @@ export function setupInteractiveHandlers(): void {
734811
gitStrategy,
735812
});
736813
} catch (err) {
737-
const userId = (body as any).user?.id;
738-
const channelId = view.private_metadata || (body as any).channel?.id;
814+
const actionBody = body as SlackActionBody;
815+
const userId = getActionUserId(actionBody);
816+
const channelId = view.private_metadata || actionBody.channel?.id;
739817
if (userId && channelId) {
740818
await client.chat.postEphemeral({
741819
channel: channelId,
@@ -746,8 +824,9 @@ export function setupInteractiveHandlers(): void {
746824
return;
747825
}
748826

749-
const userId = (body as any).user?.id;
750-
const channelId = view.private_metadata || (body as any).channel?.id;
827+
const actionBody = body as SlackActionBody;
828+
const userId = getActionUserId(actionBody);
829+
const channelId = view.private_metadata || actionBody.channel?.id;
751830
if (userId && channelId) {
752831
await client.chat.postEphemeral({
753832
channel: channelId,
@@ -761,18 +840,20 @@ export function setupInteractiveHandlers(): void {
761840
slackApp.action(/^user_choice_\d+$/, async ({ ack, body, client }) => {
762841
await ack();
763842

764-
const action = (body as any).actions?.[0];
843+
const actionBody = body as SlackActionBody;
844+
845+
const action = actionBody.actions?.[0];
765846
const value = action?.value;
766-
const channel = (body as any).channel?.id;
767-
const threadTs = (body as any).message?.thread_ts || (body as any).message?.ts;
768-
const userId = (body as any).user?.id;
769-
const messageTs = (body as any).message?.ts;
847+
const channel = actionBody.channel?.id;
848+
const threadTs = getActionThreadTs(actionBody);
849+
const userId = getActionUserId(actionBody);
850+
const messageTs = getActionMessageTs(actionBody);
770851

771852
if (!value || !channel || !threadTs) return;
772853

773854
// Update the original message to remove buttons (keep question text only)
774855
if (messageTs) {
775-
const originalText = (body as any).message?.text || "Question";
856+
const originalText = getActionMessageText(actionBody);
776857
await client.chat.update({
777858
channel,
778859
ts: messageTs,

0 commit comments

Comments
 (0)