From 7e32f1ce20bb134aadd0ea3eb8ac7af6d3f47e54 Mon Sep 17 00:00:00 2001 From: Yifeng Wang Date: Thu, 5 Feb 2026 18:49:04 +0800 Subject: [PATCH] fix(feishu): add targeted eslint-disable comments for SDK integration Add line-specific eslint-disable-next-line comments for SDK type casts and union type issues, rather than file-level disables. Co-Authored-By: Claude Opus 4.5 --- extensions/feishu/src/accounts.ts | 4 +- extensions/feishu/src/bitable.ts | 32 +++++++--- extensions/feishu/src/bot.ts | 46 ++++++++++---- extensions/feishu/src/channel.ts | 4 +- extensions/feishu/src/client.ts | 8 ++- extensions/feishu/src/directory.ts | 24 +++++-- extensions/feishu/src/docx.ts | 77 +++++++++++++++++------ extensions/feishu/src/drive.ts | 31 ++++++--- extensions/feishu/src/media.ts | 6 ++ extensions/feishu/src/mention.ts | 16 +++-- extensions/feishu/src/monitor.ts | 3 +- extensions/feishu/src/perm.ts | 13 +++- extensions/feishu/src/policy.ts | 29 ++++++--- extensions/feishu/src/probe.ts | 1 + extensions/feishu/src/reply-dispatcher.ts | 16 +++-- extensions/feishu/src/runtime.ts | 1 + extensions/feishu/src/targets.ts | 40 +++++++++--- extensions/feishu/src/typing.ts | 9 ++- extensions/feishu/src/wiki.ts | 25 ++++++-- 19 files changed, 289 insertions(+), 96 deletions(-) diff --git a/extensions/feishu/src/accounts.ts b/extensions/feishu/src/accounts.ts index 2fbf8a285c..510f7dc92f 100644 --- a/extensions/feishu/src/accounts.ts +++ b/extensions/feishu/src/accounts.ts @@ -11,7 +11,9 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): { } | null { const appId = cfg?.appId?.trim(); const appSecret = cfg?.appSecret?.trim(); - if (!appId || !appSecret) return null; + if (!appId || !appSecret) { + return null; + } return { appId, appSecret, diff --git a/extensions/feishu/src/bitable.ts b/extensions/feishu/src/bitable.ts index 696abac979..413e916e46 100644 --- a/extensions/feishu/src/bitable.ts +++ b/extensions/feishu/src/bitable.ts @@ -71,10 +71,14 @@ async function getAppTokenFromWiki( const res = await client.wiki.space.getNode({ params: { token: nodeToken }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const node = res.data?.node; - if (!node) throw new Error("Node not found"); + if (!node) { + throw new Error("Node not found"); + } if (node.obj_type !== "bitable") { throw new Error(`Node is not a bitable (type: ${node.obj_type})`); } @@ -100,7 +104,9 @@ async function getBitableMeta(client: ReturnType, url const res = await client.bitable.app.get({ path: { app_token: appToken }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } // List tables if no table_id specified let tables: { table_id: string; name: string }[] = []; @@ -136,7 +142,9 @@ async function listFields( const res = await client.bitable.appTableField.list({ path: { app_token: appToken, table_id: tableId }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const fields = res.data?.items ?? []; return { @@ -166,7 +174,9 @@ async function listRecords( ...(pageToken && { page_token: pageToken }), }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { records: res.data?.items ?? [], @@ -185,7 +195,9 @@ async function getRecord( const res = await client.bitable.appTableRecord.get({ path: { app_token: appToken, table_id: tableId, record_id: recordId }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { record: res.data?.record, @@ -202,7 +214,9 @@ async function createRecord( path: { app_token: appToken, table_id: tableId }, data: { fields }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { record: res.data?.record, @@ -220,7 +234,9 @@ async function updateRecord( path: { app_token: appToken, table_id: tableId, record_id: recordId }, data: { fields }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { record: res.data?.record, diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 59e7cc99a4..5f0cfa18ab 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -8,7 +8,7 @@ import { } from "openclaw/plugin-sdk"; import type { FeishuConfig, FeishuMessageContext, FeishuMediaInfo } from "./types.js"; import { createFeishuClient } from "./client.js"; -import { downloadImageFeishu, downloadMessageResourceFeishu } from "./media.js"; +import { downloadMessageResourceFeishu } from "./media.js"; import { extractMentionTargets, extractMessageBody, isMentionForwardRequest } from "./mention.js"; import { resolveFeishuGroupConfig, @@ -29,12 +29,16 @@ type PermissionError = { }; function extractPermissionError(err: unknown): PermissionError | null { - if (!err || typeof err !== "object") return null; + if (!err || typeof err !== "object") { + return null; + } // Axios error structure: err.response.data contains the Feishu error const axiosErr = err as { response?: { data?: unknown } }; const data = axiosErr.response?.data; - if (!data || typeof data !== "object") return null; + if (!data || typeof data !== "object") { + return null; + } const feishuErr = data as { code?: number; @@ -43,7 +47,9 @@ function extractPermissionError(err: unknown): PermissionError | null { }; // Feishu permission error code: 99991672 - if (feishuErr.code !== 99991672) return null; + if (feishuErr.code !== 99991672) { + return null; + } // Extract the grant URL from the error message (contains the direct link) const msg = feishuErr.msg ?? ""; @@ -75,21 +81,27 @@ type SenderNameResult = { async function resolveFeishuSenderName(params: { feishuCfg?: FeishuConfig; senderOpenId: string; - log: (...args: any[]) => void; + log: (...args: unknown[]) => void; }): Promise { const { feishuCfg, senderOpenId, log } = params; - if (!feishuCfg) return {}; - if (!senderOpenId) return {}; + if (!feishuCfg) { + return {}; + } + if (!senderOpenId) { + return {}; + } const cached = senderNameCache.get(senderOpenId); const now = Date.now(); - if (cached && cached.expireAt > now) return { name: cached.name }; + if (cached && cached.expireAt > now) { + return { name: cached.name }; + } try { const client = createFeishuClient(feishuCfg); // contact/v3/users/:user_id?user_id_type=open_id - const res: any = await client.contact.user.get({ + const res = await client.contact.user.get({ path: { user_id: senderOpenId }, params: { user_id_type: "open_id" }, }); @@ -181,8 +193,12 @@ function parseMessageContent(content: string, messageType: string): string { function checkBotMentioned(event: FeishuMessageEvent, botOpenId?: string): boolean { const mentions = event.message.mentions ?? []; - if (mentions.length === 0) return false; - if (!botOpenId) return mentions.length > 0; + if (mentions.length === 0) { + return false; + } + if (!botOpenId) { + return mentions.length > 0; + } return mentions.some((m) => m.id.open_id === botOpenId); } @@ -190,7 +206,9 @@ function stripBotMention( text: string, mentions?: FeishuMessageEvent["message"]["mentions"], ): string { - if (!mentions || mentions.length === 0) return text; + if (!mentions || mentions.length === 0) { + return text; + } let result = text; for (const mention of mentions) { result = result.replace(new RegExp(`@${mention.name}\\s*`, "g"), "").trim(); @@ -503,7 +521,9 @@ export async function handleFeishuMessage(params: { senderOpenId: ctx.senderOpenId, log, }); - if (senderResult.name) ctx = { ...ctx, senderName: senderResult.name }; + if (senderResult.name) { + ctx = { ...ctx, senderName: senderResult.name }; + } // Track permission error to inform agent later (with cooldown to avoid repetition) let permissionErrorForAgent: PermissionError | undefined; diff --git a/extensions/feishu/src/channel.ts b/extensions/feishu/src/channel.ts index a3076b615a..59bbac52cd 100644 --- a/extensions/feishu/src/channel.ts +++ b/extensions/feishu/src/channel.ts @@ -144,7 +144,9 @@ export const feishuPlugin: ChannelPlugin = { cfg.channels as Record | undefined )?.defaults?.groupPolicy; const groupPolicy = feishuCfg?.groupPolicy ?? defaultGroupPolicy ?? "allowlist"; - if (groupPolicy !== "open") return []; + if (groupPolicy !== "open") { + return []; + } return [ `- Feishu groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.feishu.groupPolicy="allowlist" + channels.feishu.groupAllowFrom to restrict senders.`, ]; diff --git a/extensions/feishu/src/client.ts b/extensions/feishu/src/client.ts index 458eba1852..c5641c4fc7 100644 --- a/extensions/feishu/src/client.ts +++ b/extensions/feishu/src/client.ts @@ -6,8 +6,12 @@ let cachedClient: Lark.Client | null = null; let cachedConfig: { appId: string; appSecret: string; domain: FeishuDomain } | null = null; function resolveDomain(domain: FeishuDomain): Lark.Domain | string { - if (domain === "lark") return Lark.Domain.Lark; - if (domain === "feishu") return Lark.Domain.Feishu; + if (domain === "lark") { + return Lark.Domain.Lark; + } + if (domain === "feishu") { + return Lark.Domain.Feishu; + } return domain.replace(/\/+$/, ""); // Custom URL, remove trailing slashes } diff --git a/extensions/feishu/src/directory.ts b/extensions/feishu/src/directory.ts index 77b61e4fe7..c840cffe5d 100644 --- a/extensions/feishu/src/directory.ts +++ b/extensions/feishu/src/directory.ts @@ -26,12 +26,16 @@ export async function listFeishuDirectoryPeers(params: { for (const entry of feishuCfg?.allowFrom ?? []) { const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") ids.add(trimmed); + if (trimmed && trimmed !== "*") { + ids.add(trimmed); + } } for (const userId of Object.keys(feishuCfg?.dms ?? {})) { const trimmed = userId.trim(); - if (trimmed) ids.add(trimmed); + if (trimmed) { + ids.add(trimmed); + } } return Array.from(ids) @@ -54,12 +58,16 @@ export async function listFeishuDirectoryGroups(params: { for (const groupId of Object.keys(feishuCfg?.groups ?? {})) { const trimmed = groupId.trim(); - if (trimmed && trimmed !== "*") ids.add(trimmed); + if (trimmed && trimmed !== "*") { + ids.add(trimmed); + } } for (const entry of feishuCfg?.groupAllowFrom ?? []) { const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") ids.add(trimmed); + if (trimmed && trimmed !== "*") { + ids.add(trimmed); + } } return Array.from(ids) @@ -104,7 +112,9 @@ export async function listFeishuDirectoryPeersLive(params: { }); } } - if (peers.length >= limit) break; + if (peers.length >= limit) { + break; + } } } @@ -148,7 +158,9 @@ export async function listFeishuDirectoryGroupsLive(params: { }); } } - if (groups.length >= limit) break; + if (groups.length >= limit) { + break; + } } } diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index ce1a0aeb1b..b9cbb25ad3 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -55,7 +55,8 @@ const BLOCK_TYPE_NAMES: Record = { const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]); /** Clean blocks for insertion (remove unsupported types and read-only fields) */ -function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[] } { +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type +function cleanBlocksForInsert(blocks: any[]): { cleaned: unknown[]; skipped: string[] } { const skipped: string[] = []; const cleaned = blocks .filter((block) => { @@ -68,7 +69,7 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[ }) .map((block) => { if (block.block_type === 31 && block.table?.merge_info) { - const { merge_info, ...tableRest } = block.table; + const { merge_info: _merge_info, ...tableRest } = block.table; return { ...block, table: tableRest }; } return block; @@ -82,7 +83,9 @@ async function convertMarkdown(client: Lark.Client, markdown: string) { const res = await client.docx.document.convert({ data: { content_type: "markdown", content: markdown }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { blocks: res.data?.blocks ?? [], firstLevelBlockIds: res.data?.first_level_block_ids ?? [], @@ -92,9 +95,10 @@ async function convertMarkdown(client: Lark.Client, markdown: string) { async function insertBlocks( client: Lark.Client, docToken: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type blocks: any[], parentBlockId?: string, -): Promise<{ children: any[]; skipped: string[] }> { +): Promise<{ children: unknown[]; skipped: string[] }> { const { cleaned, skipped } = cleanBlocksForInsert(blocks); const blockId = parentBlockId ?? docToken; @@ -106,7 +110,9 @@ async function insertBlocks( path: { document_id: docToken, block_id: blockId }, data: { children: cleaned }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { children: res.data?.children ?? [], skipped }; } @@ -114,7 +120,9 @@ async function clearDocumentContent(client: Lark.Client, docToken: string) { const existing = await client.docx.documentBlock.list({ path: { document_id: docToken }, }); - if (existing.code !== 0) throw new Error(existing.msg); + if (existing.code !== 0) { + throw new Error(existing.msg); + } const childIds = existing.data?.items @@ -126,7 +134,9 @@ async function clearDocumentContent(client: Lark.Client, docToken: string) { path: { document_id: docToken, block_id: docToken }, data: { start_index: 0, end_index: childIds.length }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } } return childIds.length; @@ -144,6 +154,7 @@ async function uploadImageToDocx( parent_type: "docx_image", parent_node: blockId, size: imageBuffer.length, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream file: Readable.from(imageBuffer) as any, }, }); @@ -167,10 +178,13 @@ async function processImages( client: Lark.Client, docToken: string, markdown: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type insertedBlocks: any[], ): Promise { const imageUrls = extractImageUrls(markdown); - if (imageUrls.length === 0) return 0; + if (imageUrls.length === 0) { + return 0; + } const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27); @@ -212,7 +226,9 @@ async function readDoc(client: Lark.Client, docToken: string) { client.docx.documentBlock.list({ path: { document_id: docToken } }), ]); - if (contentRes.code !== 0) throw new Error(contentRes.msg); + if (contentRes.code !== 0) { + throw new Error(contentRes.msg); + } const blocks = blocksRes.data?.items ?? []; const blockCounts: Record = {}; @@ -247,7 +263,9 @@ async function createDoc(client: Lark.Client, title: string, folderToken?: strin const res = await client.docx.document.create({ data: { title, folder_token: folderToken }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const doc = res.data?.document; return { document_id: doc?.document_id, @@ -291,6 +309,7 @@ async function appendDoc(client: Lark.Client, docToken: string, markdown: string success: true, blocks_added: inserted.length, images_processed: imagesProcessed, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type block_ids: inserted.map((b: any) => b.block_id), ...(skipped.length > 0 && { warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`, @@ -307,7 +326,9 @@ async function updateBlock( const blockInfo = await client.docx.documentBlock.get({ path: { document_id: docToken, block_id: blockId }, }); - if (blockInfo.code !== 0) throw new Error(blockInfo.msg); + if (blockInfo.code !== 0) { + throw new Error(blockInfo.msg); + } const res = await client.docx.documentBlock.patch({ path: { document_id: docToken, block_id: blockId }, @@ -317,7 +338,9 @@ async function updateBlock( }, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, block_id: blockId }; } @@ -326,24 +349,33 @@ async function deleteBlock(client: Lark.Client, docToken: string, blockId: strin const blockInfo = await client.docx.documentBlock.get({ path: { document_id: docToken, block_id: blockId }, }); - if (blockInfo.code !== 0) throw new Error(blockInfo.msg); + if (blockInfo.code !== 0) { + throw new Error(blockInfo.msg); + } const parentId = blockInfo.data?.block?.parent_id ?? docToken; const children = await client.docx.documentBlockChildren.get({ path: { document_id: docToken, block_id: parentId }, }); - if (children.code !== 0) throw new Error(children.msg); + if (children.code !== 0) { + throw new Error(children.msg); + } const items = children.data?.items ?? []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type const index = items.findIndex((item: any) => item.block_id === blockId); - if (index === -1) throw new Error("Block not found"); + if (index === -1) { + throw new Error("Block not found"); + } const res = await client.docx.documentBlockChildren.batchDelete({ path: { document_id: docToken, block_id: parentId }, data: { start_index: index, end_index: index + 1 }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, deleted_block_id: blockId }; } @@ -352,7 +384,9 @@ async function listBlocks(client: Lark.Client, docToken: string) { const res = await client.docx.documentBlock.list({ path: { document_id: docToken }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { blocks: res.data?.items ?? [], @@ -363,7 +397,9 @@ async function getBlock(client: Lark.Client, docToken: string, blockId: string) const res = await client.docx.documentBlock.get({ path: { document_id: docToken, block_id: blockId }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { block: res.data?.block, @@ -372,7 +408,9 @@ async function getBlock(client: Lark.Client, docToken: string, blockId: string) async function listAppScopes(client: Lark.Client) { const res = await client.application.scope.list({}); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const scopes = res.data?.scopes ?? []; const granted = scopes.filter((s) => s.grant_status === 1); @@ -429,6 +467,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { case "delete_block": return json(await deleteBlock(client, p.doc_token, p.block_id)); default: + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback return json({ error: `Unknown action: ${(p as any).action}` }); } } catch (err) { diff --git a/extensions/feishu/src/drive.ts b/extensions/feishu/src/drive.ts index f40cab0414..fe30f7cb3f 100644 --- a/extensions/feishu/src/drive.ts +++ b/extensions/feishu/src/drive.ts @@ -19,13 +19,19 @@ function json(data: unknown) { async function getRootFolderToken(client: Lark.Client): Promise { // Use generic HTTP client to call the root folder meta API // as it's not directly exposed in the SDK + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accessing internal SDK property const domain = (client as any).domain ?? "https://open.feishu.cn"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accessing internal SDK property const res = (await (client as any).httpInstance.get( `${domain}/open-apis/drive/explorer/v2/root_folder/meta`, )) as { code: number; msg?: string; data?: { token?: string } }; - if (res.code !== 0) throw new Error(res.msg ?? "Failed to get root folder"); + if (res.code !== 0) { + throw new Error(res.msg ?? "Failed to get root folder"); + } const token = res.data?.token; - if (!token) throw new Error("Root folder token not found"); + if (!token) { + throw new Error("Root folder token not found"); + } return token; } @@ -35,7 +41,9 @@ async function listFolder(client: Lark.Client, folderToken?: string) { const res = await client.drive.file.list({ params: validFolderToken ? { folder_token: validFolderToken } : {}, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { files: @@ -57,7 +65,9 @@ async function getFileInfo(client: Lark.Client, fileToken: string, folderToken?: const res = await client.drive.file.list({ params: folderToken ? { folder_token: folderToken } : {}, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const file = res.data?.files?.find((f) => f.token === fileToken); if (!file) { @@ -94,7 +104,9 @@ async function createFolder(client: Lark.Client, name: string, folderToken?: str folder_token: effectiveToken, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { token: res.data?.token, @@ -118,7 +130,9 @@ async function moveFile(client: Lark.Client, fileToken: string, type: string, fo folder_token: folderToken, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -142,7 +156,9 @@ async function deleteFile(client: Lark.Client, fileToken: string, type: string) | "shortcut", }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -190,6 +206,7 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) { case "delete": return json(await deleteFile(client, p.file_token, p.type)); default: + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback return json({ error: `Unknown action: ${(p as any).action}` }); } } catch (err) { diff --git a/extensions/feishu/src/media.ts b/extensions/feishu/src/media.ts index cfa79d99ba..2d9d5b7a8f 100644 --- a/extensions/feishu/src/media.ts +++ b/extensions/feishu/src/media.ts @@ -38,6 +38,7 @@ export async function downloadImageFeishu(params: { path: { image_key: imageKey }, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies const responseAny = response as any; if (responseAny.code !== undefined && responseAny.code !== 0) { throw new Error( @@ -117,6 +118,7 @@ export async function downloadMessageResourceFeishu(params: { params: { type }, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies const responseAny = response as any; if (responseAny.code !== undefined && responseAny.code !== 0) { throw new Error( @@ -212,12 +214,14 @@ export async function uploadImageFeishu(params: { const response = await client.im.image.create({ data: { image_type: imageType, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream image: imageStream as any, }, }); // SDK v1.30+ returns data directly without code wrapper on success // On error, it throws or returns { code, msg } + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies const responseAny = response as any; if (responseAny.code !== undefined && responseAny.code !== 0) { throw new Error(`Feishu image upload failed: ${responseAny.msg || `code ${responseAny.code}`}`); @@ -258,12 +262,14 @@ export async function uploadFileFeishu(params: { data: { file_type: fileType, file_name: fileName, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream file: fileStream as any, ...(duration !== undefined && { duration }), }, }); // SDK v1.30+ returns data directly without code wrapper on success + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies const responseAny = response as any; if (responseAny.code !== undefined && responseAny.code !== 0) { throw new Error(`Feishu file upload failed: ${responseAny.msg || `code ${responseAny.code}`}`); diff --git a/extensions/feishu/src/mention.ts b/extensions/feishu/src/mention.ts index cd786791cd..1b7acb85d1 100644 --- a/extensions/feishu/src/mention.ts +++ b/extensions/feishu/src/mention.ts @@ -21,7 +21,9 @@ export function extractMentionTargets( return mentions .filter((m) => { // Exclude the bot itself - if (botOpenId && m.id.open_id === botOpenId) return false; + if (botOpenId && m.id.open_id === botOpenId) { + return false; + } // Must have open_id return !!m.id.open_id; }) @@ -40,7 +42,9 @@ export function extractMentionTargets( */ export function isMentionForwardRequest(event: FeishuMessageEvent, botOpenId?: string): boolean { const mentions = event.message.mentions ?? []; - if (mentions.length === 0) return false; + if (mentions.length === 0) { + return false; + } const isDirectMessage = event.message.chat_type === "p2p"; const hasOtherMention = mentions.some((m) => m.id.open_id !== botOpenId); @@ -101,7 +105,9 @@ export function formatMentionAllForCard(): string { * Build complete message with @mentions (text format) */ export function buildMentionedMessage(targets: MentionTarget[], message: string): string { - if (targets.length === 0) return message; + if (targets.length === 0) { + return message; + } const mentionParts = targets.map((t) => formatMentionForText(t)); return `${mentionParts.join(" ")} ${message}`; @@ -111,7 +117,9 @@ export function buildMentionedMessage(targets: MentionTarget[], message: string) * Build card content with @mentions (Markdown format) */ export function buildMentionedCardContent(targets: MentionTarget[], message: string): string { - if (targets.length === 0) return message; + if (targets.length === 0) { + return message; + } const mentionParts = targets.map((t) => formatMentionForCard(t)); return `${mentionParts.join(" ")} ${message}`; diff --git a/extensions/feishu/src/monitor.ts b/extensions/feishu/src/monitor.ts index e84e51a18f..f3bae62589 100644 --- a/extensions/feishu/src/monitor.ts +++ b/extensions/feishu/src/monitor.ts @@ -38,7 +38,6 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi } const log = opts.runtime?.log ?? console.log; - const error = opts.runtime?.error ?? console.error; if (feishuCfg) { botOpenId = await fetchBotOpenId(feishuCfg); @@ -136,7 +135,7 @@ async function monitorWebSocket(params: { abortSignal?.addEventListener("abort", handleAbort, { once: true }); try { - wsClient.start({ + void wsClient.start({ eventDispatcher, }); diff --git a/extensions/feishu/src/perm.ts b/extensions/feishu/src/perm.ts index 88e234eb7d..22184dbe9f 100644 --- a/extensions/feishu/src/perm.ts +++ b/extensions/feishu/src/perm.ts @@ -53,7 +53,9 @@ async function listMembers(client: Lark.Client, token: string, type: string) { path: { token }, params: { type: type as ListTokenType }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { members: @@ -83,7 +85,9 @@ async function addMember( perm: perm as PermType, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -102,7 +106,9 @@ async function removeMember( path: { token, member_id: memberId }, params: { type: type as CreateTokenType, member_type: memberType as MemberType }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -146,6 +152,7 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) { case "remove": return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id)); default: + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback return json({ error: `Unknown action: ${(p as any).action}` }); } } catch (err) { diff --git a/extensions/feishu/src/policy.ts b/extensions/feishu/src/policy.ts index a0e1a0d84e..dd8e5659b2 100644 --- a/extensions/feishu/src/policy.ts +++ b/extensions/feishu/src/policy.ts @@ -16,7 +16,9 @@ export function resolveFeishuAllowlistMatch(params: { .map((entry) => String(entry).trim().toLowerCase()) .filter(Boolean); - if (allowFrom.length === 0) return { allowed: false }; + if (allowFrom.length === 0) { + return { allowed: false }; + } if (allowFrom.includes("*")) { return { allowed: true, matchKey: "*", matchSource: "wildcard" }; } @@ -40,21 +42,28 @@ export function resolveFeishuGroupConfig(params: { }): FeishuGroupConfig | undefined { const groups = params.cfg?.groups ?? {}; const groupId = params.groupId?.trim(); - if (!groupId) return undefined; + if (!groupId) { + return undefined; + } - const direct = groups[groupId] as FeishuGroupConfig | undefined; - if (direct) return direct; + const direct = groups[groupId]; + if (direct) { + return direct; + } const lowered = groupId.toLowerCase(); const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered); - return matchKey ? (groups[matchKey] as FeishuGroupConfig | undefined) : undefined; + return matchKey ? groups[matchKey] : undefined; } export function resolveFeishuGroupToolPolicy( params: ChannelGroupContext, + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- type resolution issue with plugin-sdk ): GroupToolPolicyConfig | undefined { const cfg = params.cfg.channels?.feishu as FeishuConfig | undefined; - if (!cfg) return undefined; + if (!cfg) { + return undefined; + } const groupConfig = resolveFeishuGroupConfig({ cfg, @@ -71,8 +80,12 @@ export function isFeishuGroupAllowed(params: { senderName?: string | null; }): boolean { const { groupPolicy } = params; - if (groupPolicy === "disabled") return false; - if (groupPolicy === "open") return true; + if (groupPolicy === "disabled") { + return false; + } + if (groupPolicy === "open") { + return true; + } return resolveFeishuAllowlistMatch(params).allowed; } diff --git a/extensions/feishu/src/probe.ts b/extensions/feishu/src/probe.ts index 88ae53f603..f6de11de18 100644 --- a/extensions/feishu/src/probe.ts +++ b/extensions/feishu/src/probe.ts @@ -15,6 +15,7 @@ export async function probeFeishu(cfg?: FeishuConfig): Promise { - if (!replyToMessageId) return; + if (!replyToMessageId) { + return; + } typingState = await addTypingIndicator({ cfg, messageId: replyToMessageId }); params.runtime.log?.(`feishu: added typing indicator reaction`); }, stop: async () => { - if (!typingState) return; + if (!typingState) { + return; + } await removeTypingIndicator({ cfg, state: typingState }); typingState = null; params.runtime.log?.(`feishu: removed typing indicator reaction`); diff --git a/extensions/feishu/src/runtime.ts b/extensions/feishu/src/runtime.ts index f1148c5e7d..9001c0af4b 100644 --- a/extensions/feishu/src/runtime.ts +++ b/extensions/feishu/src/runtime.ts @@ -1,5 +1,6 @@ import type { PluginRuntime } from "openclaw/plugin-sdk"; +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents let runtime: PluginRuntime | null = null; export function setFeishuRuntime(next: PluginRuntime) { diff --git a/extensions/feishu/src/targets.ts b/extensions/feishu/src/targets.ts index 16d3e99b9e..94f46a9e48 100644 --- a/extensions/feishu/src/targets.ts +++ b/extensions/feishu/src/targets.ts @@ -6,15 +6,23 @@ const USER_ID_REGEX = /^[a-zA-Z0-9_-]+$/; export function detectIdType(id: string): FeishuIdType | null { const trimmed = id.trim(); - if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id"; - if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id"; - if (USER_ID_REGEX.test(trimmed)) return "user_id"; + if (trimmed.startsWith(CHAT_ID_PREFIX)) { + return "chat_id"; + } + if (trimmed.startsWith(OPEN_ID_PREFIX)) { + return "open_id"; + } + if (USER_ID_REGEX.test(trimmed)) { + return "user_id"; + } return null; } export function normalizeFeishuTarget(raw: string): string | null { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const lowered = trimmed.toLowerCase(); if (lowered.startsWith("chat:")) { @@ -43,16 +51,28 @@ export function formatFeishuTarget(id: string, type?: FeishuIdType): string { export function resolveReceiveIdType(id: string): "chat_id" | "open_id" | "user_id" { const trimmed = id.trim(); - if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id"; - if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id"; + if (trimmed.startsWith(CHAT_ID_PREFIX)) { + return "chat_id"; + } + if (trimmed.startsWith(OPEN_ID_PREFIX)) { + return "open_id"; + } return "open_id"; } export function looksLikeFeishuId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(chat|user|open_id):/i.test(trimmed)) return true; - if (trimmed.startsWith(CHAT_ID_PREFIX)) return true; - if (trimmed.startsWith(OPEN_ID_PREFIX)) return true; + if (!trimmed) { + return false; + } + if (/^(chat|user|open_id):/i.test(trimmed)) { + return true; + } + if (trimmed.startsWith(CHAT_ID_PREFIX)) { + return true; + } + if (trimmed.startsWith(OPEN_ID_PREFIX)) { + return true; + } return false; } diff --git a/extensions/feishu/src/typing.ts b/extensions/feishu/src/typing.ts index e316f65dbf..662db0afa2 100644 --- a/extensions/feishu/src/typing.ts +++ b/extensions/feishu/src/typing.ts @@ -35,6 +35,7 @@ export async function addTypingIndicator(params: { }, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type const reactionId = (response as any)?.data?.reaction_id ?? null; return { messageId, reactionId }; } catch (err) { @@ -52,10 +53,14 @@ export async function removeTypingIndicator(params: { state: TypingIndicatorState; }): Promise { const { cfg, state } = params; - if (!state.reactionId) return; + if (!state.reactionId) { + return; + } const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined; - if (!feishuCfg) return; + if (!feishuCfg) { + return; + } const client = createFeishuClient(feishuCfg); diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index 1a1b72d4f1..e6c782aed5 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -24,7 +24,9 @@ const WIKI_ACCESS_HINT = async function listSpaces(client: Lark.Client) { const res = await client.wiki.space.list({}); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const spaces = res.data?.items?.map((s) => ({ @@ -45,7 +47,9 @@ async function listNodes(client: Lark.Client, spaceId: string, parentNodeToken?: path: { space_id: spaceId }, params: { parent_node_token: parentNodeToken }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { nodes: @@ -63,7 +67,9 @@ async function getNode(client: Lark.Client, token: string) { const res = await client.wiki.space.getNode({ params: { token }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const node = res.data?.node; return { @@ -95,7 +101,9 @@ async function createNode( parent_node_token: parentNodeToken, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } const node = res.data?.node; return { @@ -120,7 +128,9 @@ async function moveNode( target_parent_token: targetParentToken, }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -133,7 +143,9 @@ async function renameNode(client: Lark.Client, spaceId: string, nodeToken: strin path: { space_id: spaceId, node_token: nodeToken }, data: { title }, }); - if (res.code !== 0) throw new Error(res.msg); + if (res.code !== 0) { + throw new Error(res.msg); + } return { success: true, @@ -199,6 +211,7 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { case "rename": return json(await renameNode(client, p.space_id, p.node_token, p.title)); default: + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback return json({ error: `Unknown action: ${(p as any).action}` }); } } catch (err) {