Slack: refine scopes and onboarding
This commit is contained in:
committed by
Peter Steinberger
parent
bf3d120f8c
commit
0085b2e0a9
@@ -235,6 +235,92 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
||||
typeof slash.ephemeral === "boolean" ? slash.ephemeral : true,
|
||||
};
|
||||
|
||||
const slackDm = (slack.dm ?? {}) as Record<string, unknown>;
|
||||
const slackChannels = slack.channels;
|
||||
const slackSlash = (slack.slashCommand ?? {}) as Record<string, unknown>;
|
||||
const slackActions =
|
||||
(slack.actions ?? {}) as Partial<Record<keyof typeof defaultSlackActions, unknown>>;
|
||||
state.slackForm = {
|
||||
enabled: typeof slack.enabled === "boolean" ? slack.enabled : true,
|
||||
botToken: typeof slack.botToken === "string" ? slack.botToken : "",
|
||||
appToken: typeof slack.appToken === "string" ? slack.appToken : "",
|
||||
dmEnabled: typeof slackDm.enabled === "boolean" ? slackDm.enabled : true,
|
||||
allowFrom: toList(slackDm.allowFrom),
|
||||
groupEnabled:
|
||||
typeof slackDm.groupEnabled === "boolean" ? slackDm.groupEnabled : false,
|
||||
groupChannels: toList(slackDm.groupChannels),
|
||||
mediaMaxMb:
|
||||
typeof slack.mediaMaxMb === "number" ? String(slack.mediaMaxMb) : "",
|
||||
textChunkLimit:
|
||||
typeof slack.textChunkLimit === "number"
|
||||
? String(slack.textChunkLimit)
|
||||
: "",
|
||||
replyToMode:
|
||||
slack.replyToMode === "first" || slack.replyToMode === "all"
|
||||
? slack.replyToMode
|
||||
: "off",
|
||||
reactionNotifications:
|
||||
slack.reactionNotifications === "off" ||
|
||||
slack.reactionNotifications === "all" ||
|
||||
slack.reactionNotifications === "allowlist"
|
||||
? slack.reactionNotifications
|
||||
: "own",
|
||||
reactionAllowlist: toList(slack.reactionAllowlist),
|
||||
slashEnabled:
|
||||
typeof slackSlash.enabled === "boolean" ? slackSlash.enabled : false,
|
||||
slashName: typeof slackSlash.name === "string" ? slackSlash.name : "",
|
||||
slashSessionPrefix:
|
||||
typeof slackSlash.sessionPrefix === "string"
|
||||
? slackSlash.sessionPrefix
|
||||
: "",
|
||||
slashEphemeral:
|
||||
typeof slackSlash.ephemeral === "boolean" ? slackSlash.ephemeral : true,
|
||||
actions: {
|
||||
...defaultSlackActions,
|
||||
reactions:
|
||||
typeof slackActions.reactions === "boolean"
|
||||
? slackActions.reactions
|
||||
: defaultSlackActions.reactions,
|
||||
messages:
|
||||
typeof slackActions.messages === "boolean"
|
||||
? slackActions.messages
|
||||
: defaultSlackActions.messages,
|
||||
pins:
|
||||
typeof slackActions.pins === "boolean"
|
||||
? slackActions.pins
|
||||
: defaultSlackActions.pins,
|
||||
memberInfo:
|
||||
typeof slackActions.memberInfo === "boolean"
|
||||
? slackActions.memberInfo
|
||||
: defaultSlackActions.memberInfo,
|
||||
emojiList:
|
||||
typeof slackActions.emojiList === "boolean"
|
||||
? slackActions.emojiList
|
||||
: defaultSlackActions.emojiList,
|
||||
},
|
||||
channels: Array.isArray(slackChannels)
|
||||
? []
|
||||
: typeof slackChannels === "object" && slackChannels
|
||||
? Object.entries(slackChannels as Record<string, unknown>).map(
|
||||
([key, value]): SlackChannelForm => {
|
||||
const entry =
|
||||
value && typeof value === "object"
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
return {
|
||||
key,
|
||||
allow:
|
||||
typeof entry.allow === "boolean" ? entry.allow : true,
|
||||
requireMention:
|
||||
typeof entry.requireMention === "boolean"
|
||||
? entry.requireMention
|
||||
: false,
|
||||
};
|
||||
},
|
||||
)
|
||||
: [],
|
||||
};
|
||||
|
||||
state.signalForm = {
|
||||
enabled: typeof signal.enabled === "boolean" ? signal.enabled : true,
|
||||
account: typeof signal.account === "string" ? signal.account : "",
|
||||
@@ -281,6 +367,7 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
||||
const configInvalid = snapshot.valid === false ? "Config invalid." : null;
|
||||
state.telegramConfigStatus = configInvalid;
|
||||
state.discordConfigStatus = configInvalid;
|
||||
state.slackConfigStatus = configInvalid;
|
||||
state.signalConfigStatus = configInvalid;
|
||||
state.imessageConfigStatus = configInvalid;
|
||||
|
||||
@@ -405,3 +492,4 @@ function removePathValue(
|
||||
delete (current as Record<string, unknown>)[lastKey];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -379,6 +379,147 @@ export async function saveDiscordConfig(state: ConnectionsState) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSlackConfig(state: ConnectionsState) {
|
||||
if (!state.client || !state.connected) return;
|
||||
if (state.slackSaving) return;
|
||||
state.slackSaving = true;
|
||||
state.slackConfigStatus = null;
|
||||
try {
|
||||
const base = state.configSnapshot?.config ?? {};
|
||||
const config = { ...base } as Record<string, unknown>;
|
||||
const slack = { ...(config.slack ?? {}) } as Record<string, unknown>;
|
||||
const form = state.slackForm;
|
||||
|
||||
if (form.enabled) {
|
||||
delete slack.enabled;
|
||||
} else {
|
||||
slack.enabled = false;
|
||||
}
|
||||
|
||||
if (!state.slackTokenLocked) {
|
||||
const token = form.botToken.trim();
|
||||
if (token) slack.botToken = token;
|
||||
else delete slack.botToken;
|
||||
}
|
||||
if (!state.slackAppTokenLocked) {
|
||||
const token = form.appToken.trim();
|
||||
if (token) slack.appToken = token;
|
||||
else delete slack.appToken;
|
||||
}
|
||||
|
||||
const dm = { ...(slack.dm ?? {}) } as Record<string, unknown>;
|
||||
dm.enabled = form.dmEnabled;
|
||||
const allowFrom = parseList(form.allowFrom);
|
||||
if (allowFrom.length > 0) dm.allowFrom = allowFrom;
|
||||
else delete dm.allowFrom;
|
||||
if (form.groupEnabled) {
|
||||
dm.groupEnabled = true;
|
||||
} else {
|
||||
delete dm.groupEnabled;
|
||||
}
|
||||
const groupChannels = parseList(form.groupChannels);
|
||||
if (groupChannels.length > 0) dm.groupChannels = groupChannels;
|
||||
else delete dm.groupChannels;
|
||||
if (Object.keys(dm).length > 0) slack.dm = dm;
|
||||
else delete slack.dm;
|
||||
|
||||
const mediaMaxMb = Number.parseFloat(form.mediaMaxMb);
|
||||
if (Number.isFinite(mediaMaxMb) && mediaMaxMb > 0) {
|
||||
slack.mediaMaxMb = mediaMaxMb;
|
||||
} else {
|
||||
delete slack.mediaMaxMb;
|
||||
}
|
||||
|
||||
const textChunkLimit = Number.parseInt(form.textChunkLimit, 10);
|
||||
if (Number.isFinite(textChunkLimit) && textChunkLimit > 0) {
|
||||
slack.textChunkLimit = textChunkLimit;
|
||||
} else {
|
||||
delete slack.textChunkLimit;
|
||||
}
|
||||
|
||||
if (form.replyToMode === "off") delete slack.replyToMode;
|
||||
else slack.replyToMode = form.replyToMode;
|
||||
|
||||
if (form.reactionNotifications === "own") {
|
||||
delete slack.reactionNotifications;
|
||||
} else {
|
||||
slack.reactionNotifications = form.reactionNotifications;
|
||||
}
|
||||
const reactionAllowlist = parseList(form.reactionAllowlist);
|
||||
if (reactionAllowlist.length > 0) {
|
||||
slack.reactionAllowlist = reactionAllowlist;
|
||||
} else {
|
||||
delete slack.reactionAllowlist;
|
||||
}
|
||||
|
||||
const slash = { ...(slack.slashCommand ?? {}) } as Record<string, unknown>;
|
||||
if (form.slashEnabled) {
|
||||
slash.enabled = true;
|
||||
} else {
|
||||
delete slash.enabled;
|
||||
}
|
||||
if (form.slashName.trim()) slash.name = form.slashName.trim();
|
||||
else delete slash.name;
|
||||
if (form.slashSessionPrefix.trim())
|
||||
slash.sessionPrefix = form.slashSessionPrefix.trim();
|
||||
else delete slash.sessionPrefix;
|
||||
if (form.slashEphemeral) {
|
||||
delete slash.ephemeral;
|
||||
} else {
|
||||
slash.ephemeral = false;
|
||||
}
|
||||
if (Object.keys(slash).length > 0) slack.slashCommand = slash;
|
||||
else delete slack.slashCommand;
|
||||
|
||||
const actions: Partial<SlackActionForm> = {};
|
||||
const applyAction = (key: keyof SlackActionForm) => {
|
||||
const value = form.actions[key];
|
||||
if (value !== defaultSlackActions[key]) actions[key] = value;
|
||||
};
|
||||
applyAction("reactions");
|
||||
applyAction("messages");
|
||||
applyAction("pins");
|
||||
applyAction("memberInfo");
|
||||
applyAction("emojiList");
|
||||
if (Object.keys(actions).length > 0) {
|
||||
slack.actions = actions;
|
||||
} else {
|
||||
delete slack.actions;
|
||||
}
|
||||
|
||||
const channels = form.channels
|
||||
.map((entry): [string, Record<string, unknown>] | null => {
|
||||
const key = entry.key.trim();
|
||||
if (!key) return null;
|
||||
const record: Record<string, unknown> = {
|
||||
allow: entry.allow,
|
||||
requireMention: entry.requireMention,
|
||||
};
|
||||
return [key, record];
|
||||
})
|
||||
.filter((value): value is [string, Record<string, unknown>] => Boolean(value));
|
||||
if (channels.length > 0) {
|
||||
slack.channels = Object.fromEntries(channels);
|
||||
} else {
|
||||
delete slack.channels;
|
||||
}
|
||||
|
||||
if (Object.keys(slack).length > 0) {
|
||||
config.slack = slack;
|
||||
} else {
|
||||
delete config.slack;
|
||||
}
|
||||
|
||||
const raw = `${JSON.stringify(config, null, 2).trimEnd()}\n`;
|
||||
await state.client.request("config.set", { raw });
|
||||
state.slackConfigStatus = "Saved. Restart gateway if needed.";
|
||||
} catch (err) {
|
||||
state.slackConfigStatus = String(err);
|
||||
} finally {
|
||||
state.slackSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSignalConfig(state: ConnectionsState) {
|
||||
if (!state.client || !state.connected) return;
|
||||
if (state.signalSaving) return;
|
||||
@@ -529,3 +670,4 @@ export async function saveIMessageConfig(state: ConnectionsState) {
|
||||
state.imessageSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,41 @@ export type DiscordActionForm = {
|
||||
moderation: boolean;
|
||||
};
|
||||
|
||||
export type SlackChannelForm = {
|
||||
key: string;
|
||||
allow: boolean;
|
||||
requireMention: boolean;
|
||||
};
|
||||
|
||||
export type SlackActionForm = {
|
||||
reactions: boolean;
|
||||
messages: boolean;
|
||||
pins: boolean;
|
||||
memberInfo: boolean;
|
||||
emojiList: boolean;
|
||||
};
|
||||
|
||||
export type SlackForm = {
|
||||
enabled: boolean;
|
||||
botToken: string;
|
||||
appToken: string;
|
||||
dmEnabled: boolean;
|
||||
allowFrom: string;
|
||||
groupEnabled: boolean;
|
||||
groupChannels: string;
|
||||
mediaMaxMb: string;
|
||||
textChunkLimit: string;
|
||||
replyToMode: "off" | "first" | "all";
|
||||
reactionNotifications: "off" | "own" | "all" | "allowlist";
|
||||
reactionAllowlist: string;
|
||||
slashEnabled: boolean;
|
||||
slashName: string;
|
||||
slashSessionPrefix: string;
|
||||
slashEphemeral: boolean;
|
||||
actions: SlackActionForm;
|
||||
channels: SlackChannelForm[];
|
||||
};
|
||||
|
||||
export const defaultDiscordActions: DiscordActionForm = {
|
||||
reactions: true,
|
||||
stickers: true,
|
||||
@@ -78,6 +113,14 @@ export const defaultDiscordActions: DiscordActionForm = {
|
||||
moderation: false,
|
||||
};
|
||||
|
||||
export const defaultSlackActions: SlackActionForm = {
|
||||
reactions: true,
|
||||
messages: true,
|
||||
pins: true,
|
||||
memberInfo: true,
|
||||
emojiList: true,
|
||||
};
|
||||
|
||||
export type SignalForm = {
|
||||
enabled: boolean;
|
||||
account: string;
|
||||
@@ -125,3 +168,4 @@ export type CronFormState = {
|
||||
timeoutSeconds: string;
|
||||
postToMainPrefix: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ const discordActionOptions = [
|
||||
{ key: "moderation", label: "Moderation" },
|
||||
] satisfies Array<{ key: keyof DiscordActionForm; label: string }>;
|
||||
|
||||
const slackActionOptions = [
|
||||
{ key: "reactions", label: "Reactions" },
|
||||
{ key: "messages", label: "Messages" },
|
||||
{ key: "pins", label: "Pins" },
|
||||
{ key: "memberInfo", label: "Member info" },
|
||||
{ key: "emojiList", label: "Emoji list" },
|
||||
] satisfies Array<{ key: keyof SlackActionForm; label: string }>;
|
||||
|
||||
export type ConnectionsProps = {
|
||||
connected: boolean;
|
||||
loading: boolean;
|
||||
@@ -1347,3 +1355,4 @@ function renderProvider(
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user