chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -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;
}

View File

@@ -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({

View File

@@ -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 };

View File

@@ -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}`,

View File

@@ -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,

View File

@@ -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 () => {

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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: {

View File

@@ -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",

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 : "";

View File

@@ -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({

View File

@@ -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) : {};

View File

@@ -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, {

View File

@@ -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;

View File

@@ -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");
},
);

View File

@@ -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,

View File

@@ -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 });
};

View File

@@ -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,

View File

@@ -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}"`);

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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";

View File

@@ -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),

View File

@@ -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({

View File

@@ -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,

View File

@@ -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);
}
};

View File

@@ -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);
}
});
}

View File

@@ -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];
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);