mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 23:10:20 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -30,19 +30,25 @@ export type ResolvedSlackAccount = {
|
||||
|
||||
function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const accounts = cfg.channels?.slack?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return [];
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
|
||||
export function listSlackAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||
if (ids.length === 0) {
|
||||
return [DEFAULT_ACCOUNT_ID];
|
||||
}
|
||||
return ids.toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
export function resolveDefaultSlackAccountId(cfg: OpenClawConfig): string {
|
||||
const ids = listSlackAccountIds(cfg);
|
||||
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
|
||||
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
||||
return DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
@@ -51,7 +57,9 @@ function resolveAccountConfig(
|
||||
accountId: string,
|
||||
): SlackAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.slack?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return undefined;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return accounts[accountId] as SlackAccountConfig | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,13 +107,17 @@ export async function removeOwnSlackReactions(
|
||||
const toRemove = new Set<string>();
|
||||
for (const reaction of reactions ?? []) {
|
||||
const name = reaction?.name;
|
||||
if (!name) continue;
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
const users = reaction?.users ?? [];
|
||||
if (users.includes(userId)) {
|
||||
toRemove.add(name);
|
||||
}
|
||||
}
|
||||
if (toRemove.size === 0) return [];
|
||||
if (toRemove.size === 0) {
|
||||
return [];
|
||||
}
|
||||
await Promise.all(
|
||||
Array.from(toRemove, (name) =>
|
||||
client.reactions.remove({
|
||||
|
||||
@@ -16,12 +16,18 @@ function resolveAccountChannels(
|
||||
cfg: OpenClawConfig,
|
||||
accountId?: string | null,
|
||||
): { channels?: SlackChannels } {
|
||||
if (!accountId) return {};
|
||||
if (!accountId) {
|
||||
return {};
|
||||
}
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const accounts = cfg.channels?.slack?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return {};
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return {};
|
||||
}
|
||||
const exact = accounts[normalized];
|
||||
if (exact?.channels) return { channels: exact.channels };
|
||||
if (exact?.channels) {
|
||||
return { channels: exact.channels };
|
||||
}
|
||||
const matchKey = Object.keys(accounts).find(
|
||||
(key) => key.toLowerCase() === normalized.toLowerCase(),
|
||||
);
|
||||
@@ -33,10 +39,18 @@ export function migrateSlackChannelsInPlace(
|
||||
oldChannelId: string,
|
||||
newChannelId: string,
|
||||
): { migrated: boolean; skippedExisting: boolean } {
|
||||
if (!channels) return { migrated: false, skippedExisting: false };
|
||||
if (oldChannelId === newChannelId) return { migrated: false, skippedExisting: false };
|
||||
if (!Object.hasOwn(channels, oldChannelId)) return { migrated: false, skippedExisting: false };
|
||||
if (Object.hasOwn(channels, newChannelId)) return { migrated: false, skippedExisting: true };
|
||||
if (!channels) {
|
||||
return { migrated: false, skippedExisting: false };
|
||||
}
|
||||
if (oldChannelId === newChannelId) {
|
||||
return { migrated: false, skippedExisting: false };
|
||||
}
|
||||
if (!Object.hasOwn(channels, oldChannelId)) {
|
||||
return { migrated: false, skippedExisting: false };
|
||||
}
|
||||
if (Object.hasOwn(channels, newChannelId)) {
|
||||
return { migrated: false, skippedExisting: true };
|
||||
}
|
||||
channels[newChannelId] = channels[oldChannelId];
|
||||
delete channels[oldChannelId];
|
||||
return { migrated: true, skippedExisting: false };
|
||||
@@ -63,7 +77,9 @@ export function migrateSlackChannelConfig(params: {
|
||||
migrated = true;
|
||||
scopes.push("account");
|
||||
}
|
||||
if (result.skippedExisting) skippedExisting = true;
|
||||
if (result.skippedExisting) {
|
||||
skippedExisting = true;
|
||||
}
|
||||
}
|
||||
|
||||
const globalChannels = params.cfg.channels?.slack?.channels;
|
||||
@@ -77,7 +93,9 @@ export function migrateSlackChannelConfig(params: {
|
||||
migrated = true;
|
||||
scopes.push("global");
|
||||
}
|
||||
if (result.skippedExisting) skippedExisting = true;
|
||||
if (result.skippedExisting) {
|
||||
skippedExisting = true;
|
||||
}
|
||||
}
|
||||
|
||||
return { migrated, skippedExisting, scopes };
|
||||
|
||||
@@ -47,8 +47,12 @@ function normalizeQuery(value?: string | null): string {
|
||||
|
||||
function buildUserRank(user: SlackUser): number {
|
||||
let rank = 0;
|
||||
if (!user.deleted) rank += 2;
|
||||
if (!user.is_bot && !user.is_app_user) rank += 1;
|
||||
if (!user.deleted) {
|
||||
rank += 2;
|
||||
}
|
||||
if (!user.is_bot && !user.is_app_user) {
|
||||
rank += 1;
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
|
||||
@@ -60,7 +64,9 @@ export async function listSlackDirectoryPeersLive(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const token = resolveReadToken(params);
|
||||
if (!token) return [];
|
||||
if (!token) {
|
||||
return [];
|
||||
}
|
||||
const client = createSlackWebClient(token);
|
||||
const query = normalizeQuery(params.query);
|
||||
const members: SlackUser[] = [];
|
||||
@@ -71,7 +77,9 @@ export async function listSlackDirectoryPeersLive(
|
||||
limit: 200,
|
||||
cursor,
|
||||
})) as SlackListUsersResponse;
|
||||
if (Array.isArray(res.members)) members.push(...res.members);
|
||||
if (Array.isArray(res.members)) {
|
||||
members.push(...res.members);
|
||||
}
|
||||
const next = res.response_metadata?.next_cursor?.trim();
|
||||
cursor = next ? next : undefined;
|
||||
} while (cursor);
|
||||
@@ -83,14 +91,18 @@ export async function listSlackDirectoryPeersLive(
|
||||
const candidates = [name, handle, email]
|
||||
.map((item) => item?.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
if (!query) return true;
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
return candidates.some((candidate) => candidate?.includes(query));
|
||||
});
|
||||
|
||||
const rows = filtered
|
||||
.map((member) => {
|
||||
const id = member.id?.trim();
|
||||
if (!id) return null;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
const handle = member.name?.trim();
|
||||
const display =
|
||||
member.profile?.display_name?.trim() ||
|
||||
@@ -118,7 +130,9 @@ export async function listSlackDirectoryGroupsLive(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const token = resolveReadToken(params);
|
||||
if (!token) return [];
|
||||
if (!token) {
|
||||
return [];
|
||||
}
|
||||
const client = createSlackWebClient(token);
|
||||
const query = normalizeQuery(params.query);
|
||||
const channels: SlackChannel[] = [];
|
||||
@@ -131,14 +145,18 @@ export async function listSlackDirectoryGroupsLive(
|
||||
limit: 1000,
|
||||
cursor,
|
||||
})) as SlackListChannelsResponse;
|
||||
if (Array.isArray(res.channels)) channels.push(...res.channels);
|
||||
if (Array.isArray(res.channels)) {
|
||||
channels.push(...res.channels);
|
||||
}
|
||||
const next = res.response_metadata?.next_cursor?.trim();
|
||||
cursor = next ? next : undefined;
|
||||
} while (cursor);
|
||||
|
||||
const filtered = channels.filter((channel) => {
|
||||
const name = channel.name?.trim().toLowerCase();
|
||||
if (!query) return true;
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
return Boolean(name && name.includes(query));
|
||||
});
|
||||
|
||||
@@ -146,7 +164,9 @@ export async function listSlackDirectoryGroupsLive(
|
||||
.map((channel) => {
|
||||
const id = channel.id?.trim();
|
||||
const name = channel.name?.trim();
|
||||
if (!id || !name) return null;
|
||||
if (!id || !name) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "group",
|
||||
id: `channel:${id}`,
|
||||
|
||||
@@ -11,7 +11,9 @@ function escapeSlackMrkdwnSegment(text: string): string {
|
||||
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
|
||||
|
||||
function isAllowedSlackAngleToken(token: string): boolean {
|
||||
if (!token.startsWith("<") || !token.endsWith(">")) return false;
|
||||
if (!token.startsWith("<") || !token.endsWith(">")) {
|
||||
return false;
|
||||
}
|
||||
const inner = token.slice(1, -1);
|
||||
return (
|
||||
inner.startsWith("@") ||
|
||||
@@ -68,13 +70,17 @@ function escapeSlackMrkdwnText(text: string): string {
|
||||
|
||||
function buildSlackLink(link: MarkdownLinkSpan, text: string) {
|
||||
const href = link.href.trim();
|
||||
if (!href) return null;
|
||||
if (!href) {
|
||||
return null;
|
||||
}
|
||||
const label = text.slice(link.start, link.end);
|
||||
const trimmedLabel = label.trim();
|
||||
const comparableHref = href.startsWith("mailto:") ? href.slice("mailto:".length) : href;
|
||||
const useMarkup =
|
||||
trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref;
|
||||
if (!useMarkup) return null;
|
||||
if (!useMarkup) {
|
||||
return null;
|
||||
}
|
||||
const safeHref = escapeSlackMrkdwnSegment(href);
|
||||
return {
|
||||
start: link.start,
|
||||
|
||||
@@ -23,7 +23,9 @@ describe("registerSlackHttpHandler", () => {
|
||||
const unregisters: Array<() => void> = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const unregister of unregisters.splice(0)) unregister();
|
||||
for (const unregister of unregisters.splice(0)) {
|
||||
unregister();
|
||||
}
|
||||
});
|
||||
|
||||
it("routes requests to a registered handler", async () => {
|
||||
|
||||
@@ -16,7 +16,9 @@ const slackHttpRoutes = new Map<string, SlackHttpRequestHandler>();
|
||||
|
||||
export function normalizeSlackWebhookPath(path?: string | null): string {
|
||||
const trimmed = path?.trim();
|
||||
if (!trimmed) return "/slack/events";
|
||||
if (!trimmed) {
|
||||
return "/slack/events";
|
||||
}
|
||||
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||
}
|
||||
|
||||
@@ -39,7 +41,9 @@ export async function handleSlackHttpRequest(
|
||||
): Promise<boolean> {
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
const handler = slackHttpRoutes.get(url.pathname);
|
||||
if (!handler) return false;
|
||||
if (!handler) {
|
||||
return false;
|
||||
}
|
||||
await handler(req, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ export const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
export async function waitForSlackEvent(name: string) {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (getSlackHandlers()?.has(name)) return;
|
||||
if (getSlackHandlers()?.has(name)) {
|
||||
return;
|
||||
}
|
||||
await flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,9 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
async function waitForEvent(name: string) {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (getSlackHandlers()?.has(name)) return;
|
||||
if (getSlackHandlers()?.has(name)) {
|
||||
return;
|
||||
}
|
||||
await flush();
|
||||
}
|
||||
}
|
||||
@@ -137,7 +139,9 @@ describe("monitorSlackProvider threading", () => {
|
||||
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||
|
||||
const client = getSlackClient();
|
||||
if (!client) throw new Error("Slack client not registered");
|
||||
if (!client) {
|
||||
throw new Error("Slack client not registered");
|
||||
}
|
||||
const conversations = client.conversations as {
|
||||
history: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
@@ -154,7 +158,9 @@ describe("monitorSlackProvider threading", () => {
|
||||
|
||||
await waitForEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
|
||||
@@ -46,7 +46,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -70,7 +72,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
it("reacts to mention-gated room messages when ackReaction is enabled", async () => {
|
||||
replyMock.mockResolvedValue(undefined);
|
||||
const client = getSlackClient();
|
||||
if (!client) throw new Error("Slack client not registered");
|
||||
if (!client) {
|
||||
throw new Error("Slack client not registered");
|
||||
}
|
||||
const conversations = client.conversations as {
|
||||
info: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
@@ -87,7 +91,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -132,7 +138,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -180,7 +188,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
const baseEvent = {
|
||||
type: "message",
|
||||
|
||||
@@ -35,7 +35,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -58,7 +60,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
it("drops events with mismatched api_app_id", async () => {
|
||||
const client = getSlackClient();
|
||||
if (!client) throw new Error("Slack client not registered");
|
||||
if (!client) {
|
||||
throw new Error("Slack client not registered");
|
||||
}
|
||||
(client.auth as { test: ReturnType<typeof vi.fn> }).test.mockResolvedValue({
|
||||
user_id: "bot-user",
|
||||
team_id: "T1",
|
||||
@@ -74,7 +78,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
body: { api_app_id: "A2", team_id: "T1" },
|
||||
@@ -137,7 +143,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -185,7 +193,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -248,7 +258,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -311,7 +323,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -371,7 +385,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -416,7 +432,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -457,7 +475,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -501,7 +521,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -535,7 +557,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -581,7 +605,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
|
||||
@@ -46,7 +46,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -79,7 +81,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -130,7 +134,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -191,7 +197,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -257,7 +265,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -309,7 +319,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
@@ -355,7 +367,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
await waitForSlackEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
if (!handler) {
|
||||
throw new Error("Slack message handler not registered");
|
||||
}
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { AllowlistMatch } from "../../channels/allowlist-match.js";
|
||||
|
||||
export function normalizeSlackSlug(raw?: string) {
|
||||
const trimmed = raw?.trim().toLowerCase() ?? "";
|
||||
if (!trimmed) return "";
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const dashed = trimmed.replace(/\s+/g, "-");
|
||||
const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-");
|
||||
return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
||||
@@ -26,7 +28,9 @@ export function resolveSlackAllowListMatch(params: {
|
||||
name?: string;
|
||||
}): SlackAllowListMatch {
|
||||
const allowList = params.allowList;
|
||||
if (allowList.length === 0) return { allowed: false };
|
||||
if (allowList.length === 0) {
|
||||
return { allowed: false };
|
||||
}
|
||||
if (allowList.includes("*")) {
|
||||
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
||||
}
|
||||
@@ -42,7 +46,9 @@ export function resolveSlackAllowListMatch(params: {
|
||||
{ value: slug, source: "slug" },
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate.value) continue;
|
||||
if (!candidate.value) {
|
||||
continue;
|
||||
}
|
||||
if (allowList.includes(candidate.value)) {
|
||||
return {
|
||||
allowed: true,
|
||||
@@ -64,7 +70,9 @@ export function resolveSlackUserAllowed(params: {
|
||||
userName?: string;
|
||||
}) {
|
||||
const allowList = normalizeAllowListLower(params.allowList);
|
||||
if (allowList.length === 0) return true;
|
||||
if (allowList.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return allowListMatches({
|
||||
allowList,
|
||||
id: params.userId,
|
||||
|
||||
@@ -21,7 +21,9 @@ export type SlackChannelConfigResolved = {
|
||||
|
||||
function firstDefined<T>(...values: Array<T | undefined>) {
|
||||
for (const value of values) {
|
||||
if (typeof value !== "undefined") return value;
|
||||
if (typeof value !== "undefined") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -36,13 +38,19 @@ export function shouldEmitSlackReactionNotification(params: {
|
||||
}) {
|
||||
const { mode, botId, messageAuthorId, userId, userName, allowlist } = params;
|
||||
const effectiveMode = mode ?? "own";
|
||||
if (effectiveMode === "off") return false;
|
||||
if (effectiveMode === "off") {
|
||||
return false;
|
||||
}
|
||||
if (effectiveMode === "own") {
|
||||
if (!botId || !messageAuthorId) return false;
|
||||
if (!botId || !messageAuthorId) {
|
||||
return false;
|
||||
}
|
||||
return messageAuthorId === botId;
|
||||
}
|
||||
if (effectiveMode === "allowlist") {
|
||||
if (!Array.isArray(allowlist) || allowlist.length === 0) return false;
|
||||
if (!Array.isArray(allowlist) || allowlist.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const users = normalizeAllowListLower(allowlist);
|
||||
return allowListMatches({
|
||||
allowList: users,
|
||||
|
||||
@@ -18,10 +18,18 @@ export function inferSlackChannelType(
|
||||
channelId?: string | null,
|
||||
): SlackMessageEvent["channel_type"] | undefined {
|
||||
const trimmed = channelId?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
if (trimmed.startsWith("D")) return "im";
|
||||
if (trimmed.startsWith("C")) return "channel";
|
||||
if (trimmed.startsWith("G")) return "group";
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
if (trimmed.startsWith("D")) {
|
||||
return "im";
|
||||
}
|
||||
if (trimmed.startsWith("C")) {
|
||||
return "channel";
|
||||
}
|
||||
if (trimmed.startsWith("G")) {
|
||||
return "group";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -169,7 +177,9 @@ export function createSlackMonitorContext(params: {
|
||||
const defaultRequireMention = params.defaultRequireMention ?? true;
|
||||
|
||||
const markMessageSeen = (channelId: string | undefined, ts?: string) => {
|
||||
if (!channelId || !ts) return false;
|
||||
if (!channelId || !ts) {
|
||||
return false;
|
||||
}
|
||||
return seenMessages.check(`${channelId}:${ts}`);
|
||||
};
|
||||
|
||||
@@ -178,7 +188,9 @@ export function createSlackMonitorContext(params: {
|
||||
channelType?: string | null;
|
||||
}) => {
|
||||
const channelId = p.channelId?.trim() ?? "";
|
||||
if (!channelId) return params.mainKey;
|
||||
if (!channelId) {
|
||||
return params.mainKey;
|
||||
}
|
||||
const channelType = normalizeSlackChannelType(p.channelType, channelId);
|
||||
const isDirectMessage = channelType === "im";
|
||||
const isGroup = channelType === "mpim";
|
||||
@@ -197,7 +209,9 @@ export function createSlackMonitorContext(params: {
|
||||
|
||||
const resolveChannelName = async (channelId: string) => {
|
||||
const cached = channelCache.get(channelId);
|
||||
if (cached) return cached;
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const info = await params.app.client.conversations.info({
|
||||
token: params.botToken,
|
||||
@@ -227,7 +241,9 @@ export function createSlackMonitorContext(params: {
|
||||
|
||||
const resolveUserName = async (userId: string) => {
|
||||
const cached = userCache.get(userId);
|
||||
if (cached) return cached;
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const info = await params.app.client.users.info({
|
||||
token: params.botToken,
|
||||
@@ -248,7 +264,9 @@ export function createSlackMonitorContext(params: {
|
||||
threadTs?: string;
|
||||
status: string;
|
||||
}) => {
|
||||
if (!p.threadTs) return;
|
||||
if (!p.threadTs) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
token: params.botToken,
|
||||
channel_id: p.channelId,
|
||||
@@ -286,8 +304,12 @@ export function createSlackMonitorContext(params: {
|
||||
const isGroupDm = channelType === "mpim";
|
||||
const isRoom = channelType === "channel" || channelType === "group";
|
||||
|
||||
if (isDirectMessage && !params.dmEnabled) return false;
|
||||
if (isGroupDm && !params.groupDmEnabled) return false;
|
||||
if (isDirectMessage && !params.dmEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (isGroupDm && !params.groupDmEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isGroupDm && groupDmChannels.length > 0) {
|
||||
const allowList = normalizeAllowListLower(groupDmChannels);
|
||||
@@ -301,7 +323,9 @@ export function createSlackMonitorContext(params: {
|
||||
.map((value) => value.toLowerCase());
|
||||
const permitted =
|
||||
allowList.includes("*") || candidates.some((candidate) => allowList.includes(candidate));
|
||||
if (!permitted) return false;
|
||||
if (!permitted) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRoom && p.channelId) {
|
||||
@@ -342,7 +366,9 @@ export function createSlackMonitorContext(params: {
|
||||
};
|
||||
|
||||
const shouldDropMismatchedSlackEvent = (body: unknown) => {
|
||||
if (!body || typeof body !== "object") return false;
|
||||
if (!body || typeof body !== "object") {
|
||||
return false;
|
||||
}
|
||||
const raw = body as { api_app_id?: unknown; team_id?: unknown };
|
||||
const incomingApiAppId = typeof raw.api_app_id === "string" ? raw.api_app_id : "";
|
||||
const incomingTeamId = typeof raw.team_id === "string" ? raw.team_id : "";
|
||||
|
||||
@@ -21,7 +21,9 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext })
|
||||
"channel_created",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"channel_created">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = event as SlackChannelCreatedEvent;
|
||||
const channelId = payload.channel?.id;
|
||||
@@ -54,7 +56,9 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext })
|
||||
"channel_rename",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"channel_rename">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = event as SlackChannelRenamedEvent;
|
||||
const channelId = payload.channel?.id;
|
||||
@@ -87,12 +91,16 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext })
|
||||
"channel_id_changed",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"channel_id_changed">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = event as SlackChannelIdChangedEvent;
|
||||
const oldChannelId = payload.old_channel_id;
|
||||
const newChannelId = payload.new_channel_id;
|
||||
if (!oldChannelId || !newChannelId) return;
|
||||
if (!oldChannelId || !newChannelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channelInfo = await ctx.resolveChannelName(newChannelId);
|
||||
const label = resolveSlackChannelLabel({
|
||||
|
||||
@@ -14,7 +14,9 @@ export function registerSlackMemberEvents(params: { ctx: SlackMonitorContext })
|
||||
"member_joined_channel",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"member_joined_channel">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
const payload = event as SlackMemberChannelEvent;
|
||||
const channelId = payload.channel;
|
||||
const channelInfo = channelId ? await ctx.resolveChannelName(channelId) : {};
|
||||
@@ -52,7 +54,9 @@ export function registerSlackMemberEvents(params: { ctx: SlackMonitorContext })
|
||||
"member_left_channel",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"member_left_channel">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
const payload = event as SlackMemberChannelEvent;
|
||||
const channelId = payload.channel;
|
||||
const channelInfo = channelId ? await ctx.resolveChannelName(channelId) : {};
|
||||
|
||||
@@ -21,7 +21,9 @@ export function registerSlackMessageEvents(params: {
|
||||
|
||||
ctx.app.event("message", async ({ event, body }: SlackEventMiddlewareArgs<"message">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = event as SlackMessageEvent;
|
||||
if (message.subtype === "message_changed") {
|
||||
@@ -119,7 +121,9 @@ export function registerSlackMessageEvents(params: {
|
||||
|
||||
ctx.app.event("app_mention", async ({ event, body }: SlackEventMiddlewareArgs<"app_mention">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mention = event as SlackAppMentionEvent;
|
||||
await handleSlackMessage(mention as unknown as SlackMessageEvent, {
|
||||
|
||||
@@ -12,7 +12,9 @@ export function registerSlackPinEvents(params: { ctx: SlackMonitorContext }) {
|
||||
|
||||
ctx.app.event("pin_added", async ({ event, body }: SlackEventMiddlewareArgs<"pin_added">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = event as SlackPinEvent;
|
||||
const channelId = payload.channel_id;
|
||||
@@ -49,7 +51,9 @@ export function registerSlackPinEvents(params: { ctx: SlackMonitorContext }) {
|
||||
|
||||
ctx.app.event("pin_removed", async ({ event, body }: SlackEventMiddlewareArgs<"pin_removed">) => {
|
||||
try {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = event as SlackPinEvent;
|
||||
const channelId = payload.channel_id;
|
||||
|
||||
@@ -13,7 +13,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext }
|
||||
const handleReactionEvent = async (event: SlackReactionEvent, action: string) => {
|
||||
try {
|
||||
const item = event.item;
|
||||
if (!item || item.type !== "message") return;
|
||||
if (!item || item.type !== "message") {
|
||||
return;
|
||||
}
|
||||
|
||||
const channelInfo = item.channel ? await ctx.resolveChannelName(item.channel) : {};
|
||||
const channelType = channelInfo?.type;
|
||||
@@ -54,7 +56,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext }
|
||||
ctx.app.event(
|
||||
"reaction_added",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"reaction_added">) => {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
await handleReactionEvent(event as SlackReactionEvent, "added");
|
||||
},
|
||||
);
|
||||
@@ -62,7 +66,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext }
|
||||
ctx.app.event(
|
||||
"reaction_removed",
|
||||
async ({ event, body }: SlackEventMiddlewareArgs<"reaction_removed">) => {
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) return;
|
||||
if (ctx.shouldDropMismatchedSlackEvent(body)) {
|
||||
return;
|
||||
}
|
||||
await handleReactionEvent(event as SlackReactionEvent, "removed");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -49,7 +49,9 @@ export async function resolveSlackMedia(params: {
|
||||
const files = params.files ?? [];
|
||||
for (const file of files) {
|
||||
const url = file.url_private_download ?? file.url_private;
|
||||
if (!url) continue;
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// Note: We ignore init options because fetchWithSlackAuth handles
|
||||
// redirect behavior specially. fetchRemoteMedia only passes the URL.
|
||||
@@ -63,7 +65,9 @@ export async function resolveSlackMedia(params: {
|
||||
fetchImpl,
|
||||
filePathHint: file.name,
|
||||
});
|
||||
if (fetched.buffer.byteLength > params.maxBytes) continue;
|
||||
if (fetched.buffer.byteLength > params.maxBytes) {
|
||||
continue;
|
||||
}
|
||||
const saved = await saveMediaBuffer(
|
||||
fetched.buffer,
|
||||
fetched.contentType ?? file.mimetype,
|
||||
@@ -99,7 +103,9 @@ export async function resolveSlackThreadStarter(params: {
|
||||
}): Promise<SlackThreadStarter | null> {
|
||||
const cacheKey = `${params.channelId}:${params.threadTs}`;
|
||||
const cached = THREAD_STARTER_CACHE.get(cacheKey);
|
||||
if (cached) return cached;
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const response = (await params.client.conversations.replies({
|
||||
channel: params.channelId,
|
||||
@@ -109,7 +115,9 @@ export async function resolveSlackThreadStarter(params: {
|
||||
})) as { messages?: Array<{ text?: string; user?: string; ts?: string; files?: SlackFile[] }> };
|
||||
const message = response?.messages?.[0];
|
||||
const text = (message?.text ?? "").trim();
|
||||
if (!message || !text) return null;
|
||||
if (!message || !text) {
|
||||
return null;
|
||||
}
|
||||
const starter: SlackThreadStarter = {
|
||||
text,
|
||||
userId: message.user,
|
||||
|
||||
@@ -30,7 +30,9 @@ export function createSlackMessageHandler(params: {
|
||||
debounceMs,
|
||||
buildKey: (entry) => {
|
||||
const senderId = entry.message.user ?? entry.message.bot_id;
|
||||
if (!senderId) return null;
|
||||
if (!senderId) {
|
||||
return null;
|
||||
}
|
||||
const messageTs = entry.message.ts ?? entry.message.event_ts;
|
||||
// If Slack flags a thread reply but omits thread_ts, isolate it from root debouncing.
|
||||
const threadKey = entry.message.thread_ts
|
||||
@@ -42,13 +44,19 @@ export function createSlackMessageHandler(params: {
|
||||
},
|
||||
shouldDebounce: (entry) => {
|
||||
const text = entry.message.text ?? "";
|
||||
if (!text.trim()) return false;
|
||||
if (entry.message.files && entry.message.files.length > 0) return false;
|
||||
if (!text.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (entry.message.files && entry.message.files.length > 0) {
|
||||
return false;
|
||||
}
|
||||
return !hasControlCommand(text, ctx.cfg);
|
||||
},
|
||||
onFlush: async (entries) => {
|
||||
const last = entries.at(-1);
|
||||
if (!last) return;
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
const combinedText =
|
||||
entries.length === 1
|
||||
? (last.message.text ?? "")
|
||||
@@ -70,7 +78,9 @@ export function createSlackMessageHandler(params: {
|
||||
wasMentioned: combinedMentioned || last.opts.wasMentioned,
|
||||
},
|
||||
});
|
||||
if (!prepared) return;
|
||||
if (!prepared) {
|
||||
return;
|
||||
}
|
||||
if (entries.length > 1) {
|
||||
const ids = entries.map((entry) => entry.message.ts).filter(Boolean) as string[];
|
||||
if (ids.length > 0) {
|
||||
@@ -87,7 +97,9 @@ export function createSlackMessageHandler(params: {
|
||||
});
|
||||
|
||||
return async (message, opts) => {
|
||||
if (opts.source === "message" && message.type !== "message") return;
|
||||
if (opts.source === "message" && message.type !== "message") {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
opts.source === "message" &&
|
||||
message.subtype &&
|
||||
@@ -96,7 +108,9 @@ export function createSlackMessageHandler(params: {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (ctx.markMessageSeen(message.channel, message.ts)) return;
|
||||
if (ctx.markMessageSeen(message.channel, message.ts)) {
|
||||
return;
|
||||
}
|
||||
const resolvedMessage = await threadTsResolver.resolve({ message, source: opts.source });
|
||||
await debouncer.enqueue({ message: resolvedMessage, opts });
|
||||
};
|
||||
|
||||
@@ -67,7 +67,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
});
|
||||
},
|
||||
stop: async () => {
|
||||
if (!didSetStatus) return;
|
||||
if (!didSetStatus) {
|
||||
return;
|
||||
}
|
||||
didSetStatus = false;
|
||||
await ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
|
||||
@@ -92,7 +92,9 @@ export async function prepareSlackMessage(params: {
|
||||
|
||||
const isBotMessage = Boolean(message.bot_id);
|
||||
if (isBotMessage) {
|
||||
if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null;
|
||||
if (message.user && ctx.botUserId && message.user === ctx.botUserId) {
|
||||
return null;
|
||||
}
|
||||
if (!allowBots) {
|
||||
logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`);
|
||||
return null;
|
||||
@@ -337,7 +339,9 @@ export async function prepareSlackMessage(params: {
|
||||
maxBytes: ctx.mediaMaxBytes,
|
||||
});
|
||||
const rawBody = (message.text ?? "").trim() || media?.placeholder || "";
|
||||
if (!rawBody) return null;
|
||||
if (!rawBody) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
||||
const ackReactionValue = ackReaction ?? "";
|
||||
@@ -552,7 +556,9 @@ export async function prepareSlackMessage(params: {
|
||||
});
|
||||
|
||||
const replyTarget = ctxPayload.To ?? undefined;
|
||||
if (!replyTarget) return null;
|
||||
if (!replyTarget) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shouldLogVerbose()) {
|
||||
logVerbose(`slack inbound: channel=${message.channel} from=${slackFrom} preview="${preview}"`);
|
||||
|
||||
@@ -4,8 +4,14 @@ export function isSlackChannelAllowedByPolicy(params: {
|
||||
channelAllowed: boolean;
|
||||
}): boolean {
|
||||
const { groupPolicy, channelAllowlistConfigured, channelAllowed } = params;
|
||||
if (groupPolicy === "disabled") return false;
|
||||
if (groupPolicy === "open") return true;
|
||||
if (!channelAllowlistConfigured) return false;
|
||||
if (groupPolicy === "disabled") {
|
||||
return false;
|
||||
}
|
||||
if (groupPolicy === "open") {
|
||||
return true;
|
||||
}
|
||||
if (!channelAllowlistConfigured) {
|
||||
return false;
|
||||
}
|
||||
return channelAllowed;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ const slackBolt =
|
||||
const { App, HTTPReceiver } = slackBolt;
|
||||
function parseApiAppIdFromAppToken(raw?: string) {
|
||||
const token = raw?.trim();
|
||||
if (!token) return undefined;
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
const match = /^xapp-\d-([a-z0-9]+)-/i.exec(token);
|
||||
return match?.[1]?.toUpperCase();
|
||||
}
|
||||
@@ -220,7 +222,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
|
||||
if (resolveToken) {
|
||||
void (async () => {
|
||||
if (opts.abortSignal?.aborted) return;
|
||||
if (opts.abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (channelsConfig && Object.keys(channelsConfig).length > 0) {
|
||||
try {
|
||||
@@ -235,7 +239,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
const unresolved: string[] = [];
|
||||
for (const entry of resolved) {
|
||||
const source = channelsConfig?.[entry.input];
|
||||
if (!source) continue;
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
if (!entry.resolved || !entry.id) {
|
||||
unresolved.push(entry.input);
|
||||
continue;
|
||||
@@ -284,12 +290,18 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
if (channelsConfig && Object.keys(channelsConfig).length > 0) {
|
||||
const userEntries = new Set<string>();
|
||||
for (const channel of Object.values(channelsConfig)) {
|
||||
if (!channel || typeof channel !== "object") continue;
|
||||
if (!channel || typeof channel !== "object") {
|
||||
continue;
|
||||
}
|
||||
const channelUsers = (channel as { users?: Array<string | number> }).users;
|
||||
if (!Array.isArray(channelUsers)) continue;
|
||||
if (!Array.isArray(channelUsers)) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of channelUsers) {
|
||||
const trimmed = String(entry).trim();
|
||||
if (trimmed && trimmed !== "*") userEntries.add(trimmed);
|
||||
if (trimmed && trimmed !== "*") {
|
||||
userEntries.add(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,14 +321,20 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
|
||||
const nextChannels = { ...channelsConfig };
|
||||
for (const [channelKey, channelConfig] of Object.entries(channelsConfig)) {
|
||||
if (!channelConfig || typeof channelConfig !== "object") continue;
|
||||
if (!channelConfig || typeof channelConfig !== "object") {
|
||||
continue;
|
||||
}
|
||||
const channelUsers = (channelConfig as { users?: Array<string | number> }).users;
|
||||
if (!Array.isArray(channelUsers) || channelUsers.length === 0) continue;
|
||||
if (!Array.isArray(channelUsers) || channelUsers.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const additions: string[] = [];
|
||||
for (const entry of channelUsers) {
|
||||
const trimmed = String(entry).trim();
|
||||
const resolved = resolvedMap.get(trimmed);
|
||||
if (resolved?.resolved && resolved.id) additions.push(resolved.id);
|
||||
if (resolved?.resolved && resolved.id) {
|
||||
additions.push(resolved.id);
|
||||
}
|
||||
}
|
||||
nextChannels[channelKey] = {
|
||||
...channelConfig,
|
||||
@@ -337,7 +355,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
}
|
||||
|
||||
const stopOnAbort = () => {
|
||||
if (opts.abortSignal?.aborted && slackMode === "socket") void app.stop();
|
||||
if (opts.abortSignal?.aborted && slackMode === "socket") {
|
||||
void app.stop();
|
||||
}
|
||||
};
|
||||
opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true });
|
||||
|
||||
@@ -348,7 +368,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
} else {
|
||||
runtime.log?.(`slack http mode listening at ${slackWebhookPath}`);
|
||||
}
|
||||
if (opts.abortSignal?.aborted) return;
|
||||
if (opts.abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
opts.abortSignal?.addEventListener("abort", () => resolve(), {
|
||||
once: true,
|
||||
|
||||
@@ -21,11 +21,15 @@ export async function deliverReplies(params: {
|
||||
const threadTs = payload.replyToId ?? params.replyThreadTs;
|
||||
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const text = payload.text ?? "";
|
||||
if (!text && mediaList.length === 0) continue;
|
||||
if (!text && mediaList.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mediaList.length === 0) {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) continue;
|
||||
if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) {
|
||||
continue;
|
||||
}
|
||||
await sendMessageSlack(params.target, trimmed, {
|
||||
token: params.token,
|
||||
threadTs,
|
||||
@@ -131,7 +135,9 @@ export async function deliverSlackSlashReplies(params: {
|
||||
const combined = [text ?? "", ...mediaList.map((url) => url.trim()).filter(Boolean)]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
if (!combined) continue;
|
||||
if (!combined) {
|
||||
continue;
|
||||
}
|
||||
const chunkMode = params.chunkMode ?? "length";
|
||||
const markdownChunks =
|
||||
chunkMode === "newline"
|
||||
@@ -140,13 +146,17 @@ export async function deliverSlackSlashReplies(params: {
|
||||
const chunks = markdownChunks.flatMap((markdown) =>
|
||||
markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode: params.tableMode }),
|
||||
);
|
||||
if (!chunks.length && combined) chunks.push(combined);
|
||||
if (!chunks.length && combined) {
|
||||
chunks.push(combined);
|
||||
}
|
||||
for (const chunk of chunks) {
|
||||
messages.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length === 0) return;
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Slack slash command responses can be multi-part by sending follow-ups via response_url.
|
||||
const responseType = params.ephemeral ? "ephemeral" : "in_channel";
|
||||
|
||||
@@ -103,7 +103,9 @@ describe("Slack native command argument menus", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = commands.get("/usage");
|
||||
if (!handler) throw new Error("Missing /usage handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing /usage handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
const ack = vi.fn().mockResolvedValue(undefined);
|
||||
@@ -132,7 +134,9 @@ describe("Slack native command argument menus", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = actions.get("openclaw_cmdarg");
|
||||
if (!handler) throw new Error("Missing arg-menu action handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing arg-menu action handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
await handler({
|
||||
@@ -158,7 +162,9 @@ describe("Slack native command argument menus", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = actions.get("openclaw_cmdarg");
|
||||
if (!handler) throw new Error("Missing arg-menu action handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing arg-menu action handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
await handler({
|
||||
@@ -186,7 +192,9 @@ describe("Slack native command argument menus", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = actions.get("openclaw_cmdarg");
|
||||
if (!handler) throw new Error("Missing arg-menu action handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing arg-menu action handler");
|
||||
}
|
||||
|
||||
await handler({
|
||||
ack: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -208,7 +216,9 @@ describe("Slack native command argument menus", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = actions.get("openclaw_cmdarg");
|
||||
if (!handler) throw new Error("Missing arg-menu action handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing arg-menu action handler");
|
||||
}
|
||||
|
||||
await handler({
|
||||
ack: vi.fn().mockResolvedValue(undefined),
|
||||
|
||||
@@ -101,7 +101,9 @@ describe("slack slash commands channel policy", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = [...commands.values()][0];
|
||||
if (!handler) throw new Error("Missing slash handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing slash handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
await handler({
|
||||
@@ -133,7 +135,9 @@ describe("slack slash commands channel policy", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = [...commands.values()][0];
|
||||
if (!handler) throw new Error("Missing slash handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing slash handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
await handler({
|
||||
@@ -166,7 +170,9 @@ describe("slack slash commands channel policy", () => {
|
||||
registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
|
||||
|
||||
const handler = [...commands.values()][0];
|
||||
if (!handler) throw new Error("Missing slash handler");
|
||||
if (!handler) {
|
||||
throw new Error("Missing slash handler");
|
||||
}
|
||||
|
||||
const respond = vi.fn().mockResolvedValue(undefined);
|
||||
await handler({
|
||||
|
||||
@@ -45,7 +45,9 @@ const SLACK_COMMAND_ARG_ACTION_ID = "openclaw_cmdarg";
|
||||
const SLACK_COMMAND_ARG_VALUE_PREFIX = "cmdarg";
|
||||
|
||||
function chunkItems<T>(items: T[], size: number): T[][] {
|
||||
if (size <= 0) return [items];
|
||||
if (size <= 0) {
|
||||
return [items];
|
||||
}
|
||||
const rows: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += size) {
|
||||
rows.push(items.slice(i, i + size));
|
||||
@@ -74,11 +76,17 @@ function parseSlackCommandArgValue(raw?: string | null): {
|
||||
value: string;
|
||||
userId: string;
|
||||
} | null {
|
||||
if (!raw) return null;
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parts = raw.split("|");
|
||||
if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) return null;
|
||||
if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) {
|
||||
return null;
|
||||
}
|
||||
const [, command, arg, value, userId] = parts;
|
||||
if (!command || !arg || !value || !userId) return null;
|
||||
if (!command || !arg || !value || !userId) {
|
||||
return null;
|
||||
}
|
||||
const decode = (text: string) => {
|
||||
try {
|
||||
return decodeURIComponent(text);
|
||||
@@ -90,7 +98,9 @@ function parseSlackCommandArgValue(raw?: string | null): {
|
||||
const decodedArg = decode(arg);
|
||||
const decodedValue = decode(value);
|
||||
const decodedUserId = decode(userId);
|
||||
if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) return null;
|
||||
if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
command: decodedCommand,
|
||||
arg: decodedArg,
|
||||
@@ -163,7 +173,9 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
}
|
||||
await ack();
|
||||
|
||||
if (ctx.botUserId && command.user_id === ctx.botUserId) return;
|
||||
if (ctx.botUserId && command.user_id === ctx.botUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channelInfo = await ctx.resolveChannelName(command.channel_id);
|
||||
const channelType =
|
||||
@@ -526,7 +538,9 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
logVerbose("slack: slash commands disabled");
|
||||
}
|
||||
|
||||
if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) return;
|
||||
if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registerArgAction = (actionId: string) => {
|
||||
(
|
||||
@@ -540,7 +554,9 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
const respondFn =
|
||||
respond ??
|
||||
(async (payload: { text: string; blocks?: SlackBlock[]; response_type?: string }) => {
|
||||
if (!body.channel?.id || !body.user?.id) return;
|
||||
if (!body.channel?.id || !body.user?.id) {
|
||||
return;
|
||||
}
|
||||
await ctx.app.client.chat.postEphemeral({
|
||||
token: ctx.botToken,
|
||||
channel: body.channel.id,
|
||||
|
||||
@@ -54,7 +54,9 @@ export function createSlackThreadTsResolver(params: {
|
||||
|
||||
const getCached = (key: string, now: number) => {
|
||||
const entry = cache.get(key);
|
||||
if (!entry) return undefined;
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
if (ttlMs > 0 && now - entry.updatedAt > ttlMs) {
|
||||
cache.delete(key);
|
||||
return undefined;
|
||||
@@ -73,7 +75,9 @@ export function createSlackThreadTsResolver(params: {
|
||||
}
|
||||
while (cache.size > maxSize) {
|
||||
const oldestKey = cache.keys().next().value;
|
||||
if (!oldestKey) break;
|
||||
if (!oldestKey) {
|
||||
break;
|
||||
}
|
||||
cache.delete(oldestKey);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,13 +10,17 @@ export type SlackProbe = {
|
||||
};
|
||||
|
||||
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
||||
if (!timeoutMs || timeoutMs <= 0) return promise;
|
||||
if (!timeoutMs || timeoutMs <= 0) {
|
||||
return promise;
|
||||
}
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
const timeout = new Promise<T>((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
||||
});
|
||||
return Promise.race([promise, timeout]).finally(() => {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ type SlackListResponse = {
|
||||
|
||||
function parseSlackChannelMention(raw: string): { id?: string; name?: string } {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return {};
|
||||
if (!trimmed) {
|
||||
return {};
|
||||
}
|
||||
const mention = trimmed.match(/^<#([A-Z0-9]+)(?:\|([^>]+))?>$/i);
|
||||
if (mention) {
|
||||
const id = mention[1]?.toUpperCase();
|
||||
@@ -37,7 +39,9 @@ function parseSlackChannelMention(raw: string): { id?: string; name?: string } {
|
||||
return { id, name };
|
||||
}
|
||||
const prefixed = trimmed.replace(/^(slack:|channel:)/i, "");
|
||||
if (/^[CG][A-Z0-9]+$/i.test(prefixed)) return { id: prefixed.toUpperCase() };
|
||||
if (/^[CG][A-Z0-9]+$/i.test(prefixed)) {
|
||||
return { id: prefixed.toUpperCase() };
|
||||
}
|
||||
const name = prefixed.replace(/^#/, "").trim();
|
||||
return name ? { name } : {};
|
||||
}
|
||||
@@ -55,7 +59,9 @@ async function listSlackChannels(client: WebClient): Promise<SlackChannelLookup[
|
||||
for (const channel of res.channels ?? []) {
|
||||
const id = channel.id?.trim();
|
||||
const name = channel.name?.trim();
|
||||
if (!id || !name) continue;
|
||||
if (!id || !name) {
|
||||
continue;
|
||||
}
|
||||
channels.push({
|
||||
id,
|
||||
name,
|
||||
@@ -74,9 +80,13 @@ function resolveByName(
|
||||
channels: SlackChannelLookup[],
|
||||
): SlackChannelLookup | undefined {
|
||||
const target = name.trim().toLowerCase();
|
||||
if (!target) return undefined;
|
||||
if (!target) {
|
||||
return undefined;
|
||||
}
|
||||
const matches = channels.filter((channel) => channel.name.toLowerCase() === target);
|
||||
if (matches.length === 0) return undefined;
|
||||
if (matches.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const active = matches.find((channel) => !channel.archived);
|
||||
return active ?? matches[0];
|
||||
}
|
||||
|
||||
@@ -43,12 +43,20 @@ type SlackListUsersResponse = {
|
||||
|
||||
function parseSlackUserInput(raw: string): { id?: string; name?: string; email?: string } {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return {};
|
||||
if (!trimmed) {
|
||||
return {};
|
||||
}
|
||||
const mention = trimmed.match(/^<@([A-Z0-9]+)>$/i);
|
||||
if (mention) return { id: mention[1]?.toUpperCase() };
|
||||
if (mention) {
|
||||
return { id: mention[1]?.toUpperCase() };
|
||||
}
|
||||
const prefixed = trimmed.replace(/^(slack:|user:)/i, "");
|
||||
if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) return { id: prefixed.toUpperCase() };
|
||||
if (trimmed.includes("@") && !trimmed.startsWith("@")) return { email: trimmed.toLowerCase() };
|
||||
if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) {
|
||||
return { id: prefixed.toUpperCase() };
|
||||
}
|
||||
if (trimmed.includes("@") && !trimmed.startsWith("@")) {
|
||||
return { email: trimmed.toLowerCase() };
|
||||
}
|
||||
const name = trimmed.replace(/^@/, "").trim();
|
||||
return name ? { name } : {};
|
||||
}
|
||||
@@ -64,7 +72,9 @@ async function listSlackUsers(client: WebClient): Promise<SlackUserLookup[]> {
|
||||
for (const member of res.members ?? []) {
|
||||
const id = member.id?.trim();
|
||||
const name = member.name?.trim();
|
||||
if (!id || !name) continue;
|
||||
if (!id || !name) {
|
||||
continue;
|
||||
}
|
||||
const profile = member.profile ?? {};
|
||||
users.push({
|
||||
id,
|
||||
@@ -85,15 +95,23 @@ async function listSlackUsers(client: WebClient): Promise<SlackUserLookup[]> {
|
||||
|
||||
function scoreSlackUser(user: SlackUserLookup, match: { name?: string; email?: string }): number {
|
||||
let score = 0;
|
||||
if (!user.deleted) score += 3;
|
||||
if (!user.isBot && !user.isAppUser) score += 2;
|
||||
if (match.email && user.email === match.email) score += 5;
|
||||
if (!user.deleted) {
|
||||
score += 3;
|
||||
}
|
||||
if (!user.isBot && !user.isAppUser) {
|
||||
score += 2;
|
||||
}
|
||||
if (match.email && user.email === match.email) {
|
||||
score += 5;
|
||||
}
|
||||
if (match.name) {
|
||||
const target = match.name.toLowerCase();
|
||||
const candidates = [user.name, user.displayName, user.realName]
|
||||
.map((value) => value?.toLowerCase())
|
||||
.filter(Boolean) as string[];
|
||||
if (candidates.some((value) => value === target)) score += 2;
|
||||
if (candidates.some((value) => value === target)) {
|
||||
score += 2;
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
@@ -16,23 +16,33 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
}
|
||||
|
||||
function collectScopes(value: unknown, into: string[]) {
|
||||
if (!value) return;
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
if (typeof entry === "string" && entry.trim()) into.push(entry.trim());
|
||||
if (typeof entry === "string" && entry.trim()) {
|
||||
into.push(entry.trim());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const raw = value.trim();
|
||||
if (!raw) return;
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
const parts = raw.split(/[,\s]+/).map((part) => part.trim());
|
||||
for (const part of parts) {
|
||||
if (part) into.push(part);
|
||||
if (part) {
|
||||
into.push(part);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isRecord(value)) return;
|
||||
if (!isRecord(value)) {
|
||||
return;
|
||||
}
|
||||
for (const entry of Object.values(value)) {
|
||||
if (Array.isArray(entry) || typeof entry === "string") {
|
||||
collectScopes(entry, into);
|
||||
@@ -45,7 +55,9 @@ function normalizeScopes(scopes: string[]) {
|
||||
}
|
||||
|
||||
function extractScopes(payload: unknown): string[] {
|
||||
if (!isRecord(payload)) return [];
|
||||
if (!isRecord(payload)) {
|
||||
return [];
|
||||
}
|
||||
const scopes: string[] = [];
|
||||
collectScopes(payload.scopes, scopes);
|
||||
collectScopes(payload.scope, scopes);
|
||||
@@ -59,7 +71,9 @@ function extractScopes(payload: unknown): string[] {
|
||||
}
|
||||
|
||||
function readError(payload: unknown): string | undefined {
|
||||
if (!isRecord(payload)) return undefined;
|
||||
if (!isRecord(payload)) {
|
||||
return undefined;
|
||||
}
|
||||
const error = payload.error;
|
||||
return typeof error === "string" && error.trim() ? error.trim() : undefined;
|
||||
}
|
||||
@@ -94,7 +108,9 @@ export async function fetchSlackScopes(
|
||||
return { ok: true, scopes, source: method };
|
||||
}
|
||||
const error = readError(result);
|
||||
if (error) errors.push(`${method}: ${error}`);
|
||||
if (error) {
|
||||
errors.push(`${method}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -48,7 +48,9 @@ function resolveToken(params: {
|
||||
fallbackSource?: SlackTokenSource;
|
||||
}) {
|
||||
const explicit = resolveSlackBotToken(params.explicit);
|
||||
if (explicit) return explicit;
|
||||
if (explicit) {
|
||||
return explicit;
|
||||
}
|
||||
const fallback = resolveSlackBotToken(params.fallbackToken);
|
||||
if (!fallback) {
|
||||
logVerbose(
|
||||
@@ -161,7 +163,9 @@ export async function sendMessageSlack(
|
||||
const chunks = markdownChunks.flatMap((markdown) =>
|
||||
markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode }),
|
||||
);
|
||||
if (!chunks.length && trimmedMessage) chunks.push(trimmedMessage);
|
||||
if (!chunks.length && trimmedMessage) {
|
||||
chunks.push(trimmedMessage);
|
||||
}
|
||||
const mediaMaxBytes =
|
||||
typeof account.config.mediaMaxMb === "number"
|
||||
? account.config.mediaMaxMb * 1024 * 1024
|
||||
|
||||
@@ -18,7 +18,9 @@ export function parseSlackTarget(
|
||||
options: SlackTargetParseOptions = {},
|
||||
): SlackTarget | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i);
|
||||
if (mentionMatch) {
|
||||
return buildMessagingTarget("user", mentionMatch[1], trimmed);
|
||||
|
||||
Reference in New Issue
Block a user