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 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,9 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|||||||
} | null {
|
} | null {
|
||||||
const appId = cfg?.appId?.trim();
|
const appId = cfg?.appId?.trim();
|
||||||
const appSecret = cfg?.appSecret?.trim();
|
const appSecret = cfg?.appSecret?.trim();
|
||||||
if (!appId || !appSecret) return null;
|
if (!appId || !appSecret) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
appId,
|
appId,
|
||||||
appSecret,
|
appSecret,
|
||||||
|
|||||||
@@ -71,10 +71,14 @@ async function getAppTokenFromWiki(
|
|||||||
const res = await client.wiki.space.getNode({
|
const res = await client.wiki.space.getNode({
|
||||||
params: { token: nodeToken },
|
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;
|
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") {
|
if (node.obj_type !== "bitable") {
|
||||||
throw new Error(`Node is not a bitable (type: ${node.obj_type})`);
|
throw new Error(`Node is not a bitable (type: ${node.obj_type})`);
|
||||||
}
|
}
|
||||||
@@ -100,7 +104,9 @@ async function getBitableMeta(client: ReturnType<typeof createFeishuClient>, url
|
|||||||
const res = await client.bitable.app.get({
|
const res = await client.bitable.app.get({
|
||||||
path: { app_token: appToken },
|
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
|
// List tables if no table_id specified
|
||||||
let tables: { table_id: string; name: string }[] = [];
|
let tables: { table_id: string; name: string }[] = [];
|
||||||
@@ -136,7 +142,9 @@ async function listFields(
|
|||||||
const res = await client.bitable.appTableField.list({
|
const res = await client.bitable.appTableField.list({
|
||||||
path: { app_token: appToken, table_id: tableId },
|
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 ?? [];
|
const fields = res.data?.items ?? [];
|
||||||
return {
|
return {
|
||||||
@@ -166,7 +174,9 @@ async function listRecords(
|
|||||||
...(pageToken && { page_token: pageToken }),
|
...(pageToken && { page_token: pageToken }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records: res.data?.items ?? [],
|
records: res.data?.items ?? [],
|
||||||
@@ -185,7 +195,9 @@ async function getRecord(
|
|||||||
const res = await client.bitable.appTableRecord.get({
|
const res = await client.bitable.appTableRecord.get({
|
||||||
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
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 {
|
return {
|
||||||
record: res.data?.record,
|
record: res.data?.record,
|
||||||
@@ -202,7 +214,9 @@ async function createRecord(
|
|||||||
path: { app_token: appToken, table_id: tableId },
|
path: { app_token: appToken, table_id: tableId },
|
||||||
data: { fields },
|
data: { fields },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
record: res.data?.record,
|
record: res.data?.record,
|
||||||
@@ -220,7 +234,9 @@ async function updateRecord(
|
|||||||
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
||||||
data: { fields },
|
data: { fields },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
record: res.data?.record,
|
record: res.data?.record,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk";
|
||||||
import type { FeishuConfig, FeishuMessageContext, FeishuMediaInfo } from "./types.js";
|
import type { FeishuConfig, FeishuMessageContext, FeishuMediaInfo } from "./types.js";
|
||||||
import { createFeishuClient } from "./client.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 { extractMentionTargets, extractMessageBody, isMentionForwardRequest } from "./mention.js";
|
||||||
import {
|
import {
|
||||||
resolveFeishuGroupConfig,
|
resolveFeishuGroupConfig,
|
||||||
@@ -29,12 +29,16 @@ type PermissionError = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function extractPermissionError(err: unknown): PermissionError | null {
|
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
|
// Axios error structure: err.response.data contains the Feishu error
|
||||||
const axiosErr = err as { response?: { data?: unknown } };
|
const axiosErr = err as { response?: { data?: unknown } };
|
||||||
const data = axiosErr.response?.data;
|
const data = axiosErr.response?.data;
|
||||||
if (!data || typeof data !== "object") return null;
|
if (!data || typeof data !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const feishuErr = data as {
|
const feishuErr = data as {
|
||||||
code?: number;
|
code?: number;
|
||||||
@@ -43,7 +47,9 @@ function extractPermissionError(err: unknown): PermissionError | null {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Feishu permission error code: 99991672
|
// 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)
|
// Extract the grant URL from the error message (contains the direct link)
|
||||||
const msg = feishuErr.msg ?? "";
|
const msg = feishuErr.msg ?? "";
|
||||||
@@ -75,21 +81,27 @@ type SenderNameResult = {
|
|||||||
async function resolveFeishuSenderName(params: {
|
async function resolveFeishuSenderName(params: {
|
||||||
feishuCfg?: FeishuConfig;
|
feishuCfg?: FeishuConfig;
|
||||||
senderOpenId: string;
|
senderOpenId: string;
|
||||||
log: (...args: any[]) => void;
|
log: (...args: unknown[]) => void;
|
||||||
}): Promise<SenderNameResult> {
|
}): Promise<SenderNameResult> {
|
||||||
const { feishuCfg, senderOpenId, log } = params;
|
const { feishuCfg, senderOpenId, log } = params;
|
||||||
if (!feishuCfg) return {};
|
if (!feishuCfg) {
|
||||||
if (!senderOpenId) return {};
|
return {};
|
||||||
|
}
|
||||||
|
if (!senderOpenId) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const cached = senderNameCache.get(senderOpenId);
|
const cached = senderNameCache.get(senderOpenId);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (cached && cached.expireAt > now) return { name: cached.name };
|
if (cached && cached.expireAt > now) {
|
||||||
|
return { name: cached.name };
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = createFeishuClient(feishuCfg);
|
const client = createFeishuClient(feishuCfg);
|
||||||
|
|
||||||
// contact/v3/users/:user_id?user_id_type=open_id
|
// 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 },
|
path: { user_id: senderOpenId },
|
||||||
params: { user_id_type: "open_id" },
|
params: { user_id_type: "open_id" },
|
||||||
});
|
});
|
||||||
@@ -181,8 +193,12 @@ function parseMessageContent(content: string, messageType: string): string {
|
|||||||
|
|
||||||
function checkBotMentioned(event: FeishuMessageEvent, botOpenId?: string): boolean {
|
function checkBotMentioned(event: FeishuMessageEvent, botOpenId?: string): boolean {
|
||||||
const mentions = event.message.mentions ?? [];
|
const mentions = event.message.mentions ?? [];
|
||||||
if (mentions.length === 0) return false;
|
if (mentions.length === 0) {
|
||||||
if (!botOpenId) return mentions.length > 0;
|
return false;
|
||||||
|
}
|
||||||
|
if (!botOpenId) {
|
||||||
|
return mentions.length > 0;
|
||||||
|
}
|
||||||
return mentions.some((m) => m.id.open_id === botOpenId);
|
return mentions.some((m) => m.id.open_id === botOpenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +206,9 @@ function stripBotMention(
|
|||||||
text: string,
|
text: string,
|
||||||
mentions?: FeishuMessageEvent["message"]["mentions"],
|
mentions?: FeishuMessageEvent["message"]["mentions"],
|
||||||
): string {
|
): string {
|
||||||
if (!mentions || mentions.length === 0) return text;
|
if (!mentions || mentions.length === 0) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
let result = text;
|
let result = text;
|
||||||
for (const mention of mentions) {
|
for (const mention of mentions) {
|
||||||
result = result.replace(new RegExp(`@${mention.name}\\s*`, "g"), "").trim();
|
result = result.replace(new RegExp(`@${mention.name}\\s*`, "g"), "").trim();
|
||||||
@@ -503,7 +521,9 @@ export async function handleFeishuMessage(params: {
|
|||||||
senderOpenId: ctx.senderOpenId,
|
senderOpenId: ctx.senderOpenId,
|
||||||
log,
|
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)
|
// Track permission error to inform agent later (with cooldown to avoid repetition)
|
||||||
let permissionErrorForAgent: PermissionError | undefined;
|
let permissionErrorForAgent: PermissionError | undefined;
|
||||||
|
|||||||
@@ -144,7 +144,9 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|||||||
cfg.channels as Record<string, { groupPolicy?: string }> | undefined
|
cfg.channels as Record<string, { groupPolicy?: string }> | undefined
|
||||||
)?.defaults?.groupPolicy;
|
)?.defaults?.groupPolicy;
|
||||||
const groupPolicy = feishuCfg?.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
const groupPolicy = feishuCfg?.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||||
if (groupPolicy !== "open") return [];
|
if (groupPolicy !== "open") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
`- Feishu groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.feishu.groupPolicy="allowlist" + channels.feishu.groupAllowFrom to restrict senders.`,
|
`- Feishu groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.feishu.groupPolicy="allowlist" + channels.feishu.groupAllowFrom to restrict senders.`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ let cachedClient: Lark.Client | null = null;
|
|||||||
let cachedConfig: { appId: string; appSecret: string; domain: FeishuDomain } | null = null;
|
let cachedConfig: { appId: string; appSecret: string; domain: FeishuDomain } | null = null;
|
||||||
|
|
||||||
function resolveDomain(domain: FeishuDomain): Lark.Domain | string {
|
function resolveDomain(domain: FeishuDomain): Lark.Domain | string {
|
||||||
if (domain === "lark") return Lark.Domain.Lark;
|
if (domain === "lark") {
|
||||||
if (domain === "feishu") return Lark.Domain.Feishu;
|
return Lark.Domain.Lark;
|
||||||
|
}
|
||||||
|
if (domain === "feishu") {
|
||||||
|
return Lark.Domain.Feishu;
|
||||||
|
}
|
||||||
return domain.replace(/\/+$/, ""); // Custom URL, remove trailing slashes
|
return domain.replace(/\/+$/, ""); // Custom URL, remove trailing slashes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ export async function listFeishuDirectoryPeers(params: {
|
|||||||
|
|
||||||
for (const entry of feishuCfg?.allowFrom ?? []) {
|
for (const entry of feishuCfg?.allowFrom ?? []) {
|
||||||
const trimmed = String(entry).trim();
|
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 ?? {})) {
|
for (const userId of Object.keys(feishuCfg?.dms ?? {})) {
|
||||||
const trimmed = userId.trim();
|
const trimmed = userId.trim();
|
||||||
if (trimmed) ids.add(trimmed);
|
if (trimmed) {
|
||||||
|
ids.add(trimmed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(ids)
|
return Array.from(ids)
|
||||||
@@ -54,12 +58,16 @@ export async function listFeishuDirectoryGroups(params: {
|
|||||||
|
|
||||||
for (const groupId of Object.keys(feishuCfg?.groups ?? {})) {
|
for (const groupId of Object.keys(feishuCfg?.groups ?? {})) {
|
||||||
const trimmed = groupId.trim();
|
const trimmed = groupId.trim();
|
||||||
if (trimmed && trimmed !== "*") ids.add(trimmed);
|
if (trimmed && trimmed !== "*") {
|
||||||
|
ids.add(trimmed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of feishuCfg?.groupAllowFrom ?? []) {
|
for (const entry of feishuCfg?.groupAllowFrom ?? []) {
|
||||||
const trimmed = String(entry).trim();
|
const trimmed = String(entry).trim();
|
||||||
if (trimmed && trimmed !== "*") ids.add(trimmed);
|
if (trimmed && trimmed !== "*") {
|
||||||
|
ids.add(trimmed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(ids)
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ const BLOCK_TYPE_NAMES: Record<number, string> = {
|
|||||||
const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
|
const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
|
||||||
|
|
||||||
/** Clean blocks for insertion (remove unsupported types and read-only fields) */
|
/** 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 skipped: string[] = [];
|
||||||
const cleaned = blocks
|
const cleaned = blocks
|
||||||
.filter((block) => {
|
.filter((block) => {
|
||||||
@@ -68,7 +69,7 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[
|
|||||||
})
|
})
|
||||||
.map((block) => {
|
.map((block) => {
|
||||||
if (block.block_type === 31 && block.table?.merge_info) {
|
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, table: tableRest };
|
||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
@@ -82,7 +83,9 @@ async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|||||||
const res = await client.docx.document.convert({
|
const res = await client.docx.document.convert({
|
||||||
data: { content_type: "markdown", content: markdown },
|
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 {
|
return {
|
||||||
blocks: res.data?.blocks ?? [],
|
blocks: res.data?.blocks ?? [],
|
||||||
firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
|
firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
|
||||||
@@ -92,9 +95,10 @@ async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|||||||
async function insertBlocks(
|
async function insertBlocks(
|
||||||
client: Lark.Client,
|
client: Lark.Client,
|
||||||
docToken: string,
|
docToken: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
||||||
blocks: any[],
|
blocks: any[],
|
||||||
parentBlockId?: string,
|
parentBlockId?: string,
|
||||||
): Promise<{ children: any[]; skipped: string[] }> {
|
): Promise<{ children: unknown[]; skipped: string[] }> {
|
||||||
const { cleaned, skipped } = cleanBlocksForInsert(blocks);
|
const { cleaned, skipped } = cleanBlocksForInsert(blocks);
|
||||||
const blockId = parentBlockId ?? docToken;
|
const blockId = parentBlockId ?? docToken;
|
||||||
|
|
||||||
@@ -106,7 +110,9 @@ async function insertBlocks(
|
|||||||
path: { document_id: docToken, block_id: blockId },
|
path: { document_id: docToken, block_id: blockId },
|
||||||
data: { children: cleaned },
|
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 };
|
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({
|
const existing = await client.docx.documentBlock.list({
|
||||||
path: { document_id: docToken },
|
path: { document_id: docToken },
|
||||||
});
|
});
|
||||||
if (existing.code !== 0) throw new Error(existing.msg);
|
if (existing.code !== 0) {
|
||||||
|
throw new Error(existing.msg);
|
||||||
|
}
|
||||||
|
|
||||||
const childIds =
|
const childIds =
|
||||||
existing.data?.items
|
existing.data?.items
|
||||||
@@ -126,7 +134,9 @@ async function clearDocumentContent(client: Lark.Client, docToken: string) {
|
|||||||
path: { document_id: docToken, block_id: docToken },
|
path: { document_id: docToken, block_id: docToken },
|
||||||
data: { start_index: 0, end_index: childIds.length },
|
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;
|
return childIds.length;
|
||||||
@@ -144,6 +154,7 @@ async function uploadImageToDocx(
|
|||||||
parent_type: "docx_image",
|
parent_type: "docx_image",
|
||||||
parent_node: blockId,
|
parent_node: blockId,
|
||||||
size: imageBuffer.length,
|
size: imageBuffer.length,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream
|
||||||
file: Readable.from(imageBuffer) as any,
|
file: Readable.from(imageBuffer) as any,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -167,10 +178,13 @@ async function processImages(
|
|||||||
client: Lark.Client,
|
client: Lark.Client,
|
||||||
docToken: string,
|
docToken: string,
|
||||||
markdown: string,
|
markdown: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
||||||
insertedBlocks: any[],
|
insertedBlocks: any[],
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const imageUrls = extractImageUrls(markdown);
|
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);
|
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 } }),
|
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 blocks = blocksRes.data?.items ?? [];
|
||||||
const blockCounts: Record<string, number> = {};
|
const blockCounts: Record<string, number> = {};
|
||||||
@@ -247,7 +263,9 @@ async function createDoc(client: Lark.Client, title: string, folderToken?: strin
|
|||||||
const res = await client.docx.document.create({
|
const res = await client.docx.document.create({
|
||||||
data: { title, folder_token: folderToken },
|
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;
|
const doc = res.data?.document;
|
||||||
return {
|
return {
|
||||||
document_id: doc?.document_id,
|
document_id: doc?.document_id,
|
||||||
@@ -291,6 +309,7 @@ async function appendDoc(client: Lark.Client, docToken: string, markdown: string
|
|||||||
success: true,
|
success: true,
|
||||||
blocks_added: inserted.length,
|
blocks_added: inserted.length,
|
||||||
images_processed: imagesProcessed,
|
images_processed: imagesProcessed,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
||||||
block_ids: inserted.map((b: any) => b.block_id),
|
block_ids: inserted.map((b: any) => b.block_id),
|
||||||
...(skipped.length > 0 && {
|
...(skipped.length > 0 && {
|
||||||
warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
|
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({
|
const blockInfo = await client.docx.documentBlock.get({
|
||||||
path: { document_id: docToken, block_id: blockId },
|
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({
|
const res = await client.docx.documentBlock.patch({
|
||||||
path: { document_id: docToken, block_id: blockId },
|
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 };
|
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({
|
const blockInfo = await client.docx.documentBlock.get({
|
||||||
path: { document_id: docToken, block_id: blockId },
|
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 parentId = blockInfo.data?.block?.parent_id ?? docToken;
|
||||||
|
|
||||||
const children = await client.docx.documentBlockChildren.get({
|
const children = await client.docx.documentBlockChildren.get({
|
||||||
path: { document_id: docToken, block_id: parentId },
|
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 ?? [];
|
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);
|
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({
|
const res = await client.docx.documentBlockChildren.batchDelete({
|
||||||
path: { document_id: docToken, block_id: parentId },
|
path: { document_id: docToken, block_id: parentId },
|
||||||
data: { start_index: index, end_index: index + 1 },
|
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 };
|
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({
|
const res = await client.docx.documentBlock.list({
|
||||||
path: { document_id: docToken },
|
path: { document_id: docToken },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
blocks: res.data?.items ?? [],
|
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({
|
const res = await client.docx.documentBlock.get({
|
||||||
path: { document_id: docToken, block_id: blockId },
|
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 {
|
return {
|
||||||
block: res.data?.block,
|
block: res.data?.block,
|
||||||
@@ -372,7 +408,9 @@ async function getBlock(client: Lark.Client, docToken: string, blockId: string)
|
|||||||
|
|
||||||
async function listAppScopes(client: Lark.Client) {
|
async function listAppScopes(client: Lark.Client) {
|
||||||
const res = await client.application.scope.list({});
|
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 scopes = res.data?.scopes ?? [];
|
||||||
const granted = scopes.filter((s) => s.grant_status === 1);
|
const granted = scopes.filter((s) => s.grant_status === 1);
|
||||||
@@ -429,6 +467,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|||||||
case "delete_block":
|
case "delete_block":
|
||||||
return json(await deleteBlock(client, p.doc_token, p.block_id));
|
return json(await deleteBlock(client, p.doc_token, p.block_id));
|
||||||
default:
|
default:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
||||||
return json({ error: `Unknown action: ${(p as any).action}` });
|
return json({ error: `Unknown action: ${(p as any).action}` });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -19,13 +19,19 @@ function json(data: unknown) {
|
|||||||
async function getRootFolderToken(client: Lark.Client): Promise<string> {
|
async function getRootFolderToken(client: Lark.Client): Promise<string> {
|
||||||
// Use generic HTTP client to call the root folder meta API
|
// Use generic HTTP client to call the root folder meta API
|
||||||
// as it's not directly exposed in the SDK
|
// 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";
|
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(
|
const res = (await (client as any).httpInstance.get(
|
||||||
`${domain}/open-apis/drive/explorer/v2/root_folder/meta`,
|
`${domain}/open-apis/drive/explorer/v2/root_folder/meta`,
|
||||||
)) as { code: number; msg?: string; data?: { token?: string } };
|
)) 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;
|
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;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +41,9 @@ async function listFolder(client: Lark.Client, folderToken?: string) {
|
|||||||
const res = await client.drive.file.list({
|
const res = await client.drive.file.list({
|
||||||
params: validFolderToken ? { folder_token: validFolderToken } : {},
|
params: validFolderToken ? { folder_token: validFolderToken } : {},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
files:
|
files:
|
||||||
@@ -57,7 +65,9 @@ async function getFileInfo(client: Lark.Client, fileToken: string, folderToken?:
|
|||||||
const res = await client.drive.file.list({
|
const res = await client.drive.file.list({
|
||||||
params: folderToken ? { folder_token: folderToken } : {},
|
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);
|
const file = res.data?.files?.find((f) => f.token === fileToken);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -94,7 +104,9 @@ async function createFolder(client: Lark.Client, name: string, folderToken?: str
|
|||||||
folder_token: effectiveToken,
|
folder_token: effectiveToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: res.data?.token,
|
token: res.data?.token,
|
||||||
@@ -118,7 +130,9 @@ async function moveFile(client: Lark.Client, fileToken: string, type: string, fo
|
|||||||
folder_token: folderToken,
|
folder_token: folderToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -142,7 +156,9 @@ async function deleteFile(client: Lark.Client, fileToken: string, type: string)
|
|||||||
| "shortcut",
|
| "shortcut",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -190,6 +206,7 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) {
|
|||||||
case "delete":
|
case "delete":
|
||||||
return json(await deleteFile(client, p.file_token, p.type));
|
return json(await deleteFile(client, p.file_token, p.type));
|
||||||
default:
|
default:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
||||||
return json({ error: `Unknown action: ${(p as any).action}` });
|
return json({ error: `Unknown action: ${(p as any).action}` });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export async function downloadImageFeishu(params: {
|
|||||||
path: { image_key: imageKey },
|
path: { image_key: imageKey },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies
|
||||||
const responseAny = response as any;
|
const responseAny = response as any;
|
||||||
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -117,6 +118,7 @@ export async function downloadMessageResourceFeishu(params: {
|
|||||||
params: { type },
|
params: { type },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type varies
|
||||||
const responseAny = response as any;
|
const responseAny = response as any;
|
||||||
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -212,12 +214,14 @@ export async function uploadImageFeishu(params: {
|
|||||||
const response = await client.im.image.create({
|
const response = await client.im.image.create({
|
||||||
data: {
|
data: {
|
||||||
image_type: imageType,
|
image_type: imageType,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream
|
||||||
image: imageStream as any,
|
image: imageStream as any,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// SDK v1.30+ returns data directly without code wrapper on success
|
// SDK v1.30+ returns data directly without code wrapper on success
|
||||||
// On error, it throws or returns { code, msg }
|
// 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;
|
const responseAny = response as any;
|
||||||
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
||||||
throw new Error(`Feishu image upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
|
throw new Error(`Feishu image upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
|
||||||
@@ -258,12 +262,14 @@ export async function uploadFileFeishu(params: {
|
|||||||
data: {
|
data: {
|
||||||
file_type: fileType,
|
file_type: fileType,
|
||||||
file_name: fileName,
|
file_name: fileName,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK expects stream
|
||||||
file: fileStream as any,
|
file: fileStream as any,
|
||||||
...(duration !== undefined && { duration }),
|
...(duration !== undefined && { duration }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// SDK v1.30+ returns data directly without code wrapper on success
|
// 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;
|
const responseAny = response as any;
|
||||||
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
if (responseAny.code !== undefined && responseAny.code !== 0) {
|
||||||
throw new Error(`Feishu file upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
|
throw new Error(`Feishu file upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export function extractMentionTargets(
|
|||||||
return mentions
|
return mentions
|
||||||
.filter((m) => {
|
.filter((m) => {
|
||||||
// Exclude the bot itself
|
// 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
|
// Must have open_id
|
||||||
return !!m.id.open_id;
|
return !!m.id.open_id;
|
||||||
})
|
})
|
||||||
@@ -40,7 +42,9 @@ export function extractMentionTargets(
|
|||||||
*/
|
*/
|
||||||
export function isMentionForwardRequest(event: FeishuMessageEvent, botOpenId?: string): boolean {
|
export function isMentionForwardRequest(event: FeishuMessageEvent, botOpenId?: string): boolean {
|
||||||
const mentions = event.message.mentions ?? [];
|
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 isDirectMessage = event.message.chat_type === "p2p";
|
||||||
const hasOtherMention = mentions.some((m) => m.id.open_id !== botOpenId);
|
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)
|
* Build complete message with @mentions (text format)
|
||||||
*/
|
*/
|
||||||
export function buildMentionedMessage(targets: MentionTarget[], message: string): string {
|
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));
|
const mentionParts = targets.map((t) => formatMentionForText(t));
|
||||||
return `${mentionParts.join(" ")} ${message}`;
|
return `${mentionParts.join(" ")} ${message}`;
|
||||||
@@ -111,7 +117,9 @@ export function buildMentionedMessage(targets: MentionTarget[], message: string)
|
|||||||
* Build card content with @mentions (Markdown format)
|
* Build card content with @mentions (Markdown format)
|
||||||
*/
|
*/
|
||||||
export function buildMentionedCardContent(targets: MentionTarget[], message: string): string {
|
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));
|
const mentionParts = targets.map((t) => formatMentionForCard(t));
|
||||||
return `${mentionParts.join(" ")} ${message}`;
|
return `${mentionParts.join(" ")} ${message}`;
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const log = opts.runtime?.log ?? console.log;
|
const log = opts.runtime?.log ?? console.log;
|
||||||
const error = opts.runtime?.error ?? console.error;
|
|
||||||
|
|
||||||
if (feishuCfg) {
|
if (feishuCfg) {
|
||||||
botOpenId = await fetchBotOpenId(feishuCfg);
|
botOpenId = await fetchBotOpenId(feishuCfg);
|
||||||
@@ -136,7 +135,7 @@ async function monitorWebSocket(params: {
|
|||||||
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wsClient.start({
|
void wsClient.start({
|
||||||
eventDispatcher,
|
eventDispatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ async function listMembers(client: Lark.Client, token: string, type: string) {
|
|||||||
path: { token },
|
path: { token },
|
||||||
params: { type: type as ListTokenType },
|
params: { type: type as ListTokenType },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members:
|
members:
|
||||||
@@ -83,7 +85,9 @@ async function addMember(
|
|||||||
perm: perm as PermType,
|
perm: perm as PermType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -102,7 +106,9 @@ async function removeMember(
|
|||||||
path: { token, member_id: memberId },
|
path: { token, member_id: memberId },
|
||||||
params: { type: type as CreateTokenType, member_type: memberType as MemberType },
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -146,6 +152,7 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) {
|
|||||||
case "remove":
|
case "remove":
|
||||||
return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
|
return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
|
||||||
default:
|
default:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
||||||
return json({ error: `Unknown action: ${(p as any).action}` });
|
return json({ error: `Unknown action: ${(p as any).action}` });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export function resolveFeishuAllowlistMatch(params: {
|
|||||||
.map((entry) => String(entry).trim().toLowerCase())
|
.map((entry) => String(entry).trim().toLowerCase())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
if (allowFrom.length === 0) return { allowed: false };
|
if (allowFrom.length === 0) {
|
||||||
|
return { allowed: false };
|
||||||
|
}
|
||||||
if (allowFrom.includes("*")) {
|
if (allowFrom.includes("*")) {
|
||||||
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
||||||
}
|
}
|
||||||
@@ -40,21 +42,28 @@ export function resolveFeishuGroupConfig(params: {
|
|||||||
}): FeishuGroupConfig | undefined {
|
}): FeishuGroupConfig | undefined {
|
||||||
const groups = params.cfg?.groups ?? {};
|
const groups = params.cfg?.groups ?? {};
|
||||||
const groupId = params.groupId?.trim();
|
const groupId = params.groupId?.trim();
|
||||||
if (!groupId) return undefined;
|
if (!groupId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const direct = groups[groupId] as FeishuGroupConfig | undefined;
|
const direct = groups[groupId];
|
||||||
if (direct) return direct;
|
if (direct) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
const lowered = groupId.toLowerCase();
|
const lowered = groupId.toLowerCase();
|
||||||
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
|
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(
|
export function resolveFeishuGroupToolPolicy(
|
||||||
params: ChannelGroupContext,
|
params: ChannelGroupContext,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- type resolution issue with plugin-sdk
|
||||||
): GroupToolPolicyConfig | undefined {
|
): GroupToolPolicyConfig | undefined {
|
||||||
const cfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
const cfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
||||||
if (!cfg) return undefined;
|
if (!cfg) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const groupConfig = resolveFeishuGroupConfig({
|
const groupConfig = resolveFeishuGroupConfig({
|
||||||
cfg,
|
cfg,
|
||||||
@@ -71,8 +80,12 @@ export function isFeishuGroupAllowed(params: {
|
|||||||
senderName?: string | null;
|
senderName?: string | null;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
const { groupPolicy } = params;
|
const { groupPolicy } = params;
|
||||||
if (groupPolicy === "disabled") return false;
|
if (groupPolicy === "disabled") {
|
||||||
if (groupPolicy === "open") return true;
|
return false;
|
||||||
|
}
|
||||||
|
if (groupPolicy === "open") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return resolveFeishuAllowlistMatch(params).allowed;
|
return resolveFeishuAllowlistMatch(params).allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export async function probeFeishu(cfg?: FeishuConfig): Promise<FeishuProbeResult
|
|||||||
const client = createFeishuClient(cfg!);
|
const client = createFeishuClient(cfg!);
|
||||||
// Use im.chat.list as a simple connectivity test
|
// Use im.chat.list as a simple connectivity test
|
||||||
// The bot info API path varies by SDK version
|
// The bot info API path varies by SDK version
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accessing internal SDK method
|
||||||
const response = await (client as any).request({
|
const response = await (client as any).request({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/open-apis/bot/v3/info",
|
url: "/open-apis/bot/v3/info",
|
||||||
|
|||||||
@@ -18,9 +18,13 @@ import { addTypingIndicator, removeTypingIndicator, type TypingIndicatorState }
|
|||||||
*/
|
*/
|
||||||
function shouldUseCard(text: string): boolean {
|
function shouldUseCard(text: string): boolean {
|
||||||
// Code blocks (fenced)
|
// Code blocks (fenced)
|
||||||
if (/```[\s\S]*?```/.test(text)) return true;
|
if (/```[\s\S]*?```/.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Tables (at least header + separator row with |)
|
// Tables (at least header + separator row with |)
|
||||||
if (/\|.+\|[\r\n]+\|[-:| ]+\|/.test(text)) return true;
|
if (/\|.+\|[\r\n]+\|[-:| ]+\|/.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,12 +53,16 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
|||||||
|
|
||||||
const typingCallbacks = createTypingCallbacks({
|
const typingCallbacks = createTypingCallbacks({
|
||||||
start: async () => {
|
start: async () => {
|
||||||
if (!replyToMessageId) return;
|
if (!replyToMessageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
typingState = await addTypingIndicator({ cfg, messageId: replyToMessageId });
|
typingState = await addTypingIndicator({ cfg, messageId: replyToMessageId });
|
||||||
params.runtime.log?.(`feishu: added typing indicator reaction`);
|
params.runtime.log?.(`feishu: added typing indicator reaction`);
|
||||||
},
|
},
|
||||||
stop: async () => {
|
stop: async () => {
|
||||||
if (!typingState) return;
|
if (!typingState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await removeTypingIndicator({ cfg, state: typingState });
|
await removeTypingIndicator({ cfg, state: typingState });
|
||||||
typingState = null;
|
typingState = null;
|
||||||
params.runtime.log?.(`feishu: removed typing indicator reaction`);
|
params.runtime.log?.(`feishu: removed typing indicator reaction`);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||||
let runtime: PluginRuntime | null = null;
|
let runtime: PluginRuntime | null = null;
|
||||||
|
|
||||||
export function setFeishuRuntime(next: PluginRuntime) {
|
export function setFeishuRuntime(next: PluginRuntime) {
|
||||||
|
|||||||
@@ -6,15 +6,23 @@ const USER_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|||||||
|
|
||||||
export function detectIdType(id: string): FeishuIdType | null {
|
export function detectIdType(id: string): FeishuIdType | null {
|
||||||
const trimmed = id.trim();
|
const trimmed = id.trim();
|
||||||
if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id";
|
if (trimmed.startsWith(CHAT_ID_PREFIX)) {
|
||||||
if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id";
|
return "chat_id";
|
||||||
if (USER_ID_REGEX.test(trimmed)) return "user_id";
|
}
|
||||||
|
if (trimmed.startsWith(OPEN_ID_PREFIX)) {
|
||||||
|
return "open_id";
|
||||||
|
}
|
||||||
|
if (USER_ID_REGEX.test(trimmed)) {
|
||||||
|
return "user_id";
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeFeishuTarget(raw: string): string | null {
|
export function normalizeFeishuTarget(raw: string): string | null {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) return null;
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const lowered = trimmed.toLowerCase();
|
const lowered = trimmed.toLowerCase();
|
||||||
if (lowered.startsWith("chat:")) {
|
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" {
|
export function resolveReceiveIdType(id: string): "chat_id" | "open_id" | "user_id" {
|
||||||
const trimmed = id.trim();
|
const trimmed = id.trim();
|
||||||
if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id";
|
if (trimmed.startsWith(CHAT_ID_PREFIX)) {
|
||||||
if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id";
|
return "chat_id";
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith(OPEN_ID_PREFIX)) {
|
||||||
|
return "open_id";
|
||||||
|
}
|
||||||
return "open_id";
|
return "open_id";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function looksLikeFeishuId(raw: string): boolean {
|
export function looksLikeFeishuId(raw: string): boolean {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) return false;
|
if (!trimmed) {
|
||||||
if (/^(chat|user|open_id):/i.test(trimmed)) return true;
|
return false;
|
||||||
if (trimmed.startsWith(CHAT_ID_PREFIX)) return true;
|
}
|
||||||
if (trimmed.startsWith(OPEN_ID_PREFIX)) return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
const reactionId = (response as any)?.data?.reaction_id ?? null;
|
||||||
return { messageId, reactionId };
|
return { messageId, reactionId };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -52,10 +53,14 @@ export async function removeTypingIndicator(params: {
|
|||||||
state: TypingIndicatorState;
|
state: TypingIndicatorState;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { cfg, state } = params;
|
const { cfg, state } = params;
|
||||||
if (!state.reactionId) return;
|
if (!state.reactionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||||
if (!feishuCfg) return;
|
if (!feishuCfg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const client = createFeishuClient(feishuCfg);
|
const client = createFeishuClient(feishuCfg);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ const WIKI_ACCESS_HINT =
|
|||||||
|
|
||||||
async function listSpaces(client: Lark.Client) {
|
async function listSpaces(client: Lark.Client) {
|
||||||
const res = await client.wiki.space.list({});
|
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 =
|
const spaces =
|
||||||
res.data?.items?.map((s) => ({
|
res.data?.items?.map((s) => ({
|
||||||
@@ -45,7 +47,9 @@ async function listNodes(client: Lark.Client, spaceId: string, parentNodeToken?:
|
|||||||
path: { space_id: spaceId },
|
path: { space_id: spaceId },
|
||||||
params: { parent_node_token: parentNodeToken },
|
params: { parent_node_token: parentNodeToken },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes:
|
nodes:
|
||||||
@@ -63,7 +67,9 @@ async function getNode(client: Lark.Client, token: string) {
|
|||||||
const res = await client.wiki.space.getNode({
|
const res = await client.wiki.space.getNode({
|
||||||
params: { token },
|
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;
|
const node = res.data?.node;
|
||||||
return {
|
return {
|
||||||
@@ -95,7 +101,9 @@ async function createNode(
|
|||||||
parent_node_token: parentNodeToken,
|
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;
|
const node = res.data?.node;
|
||||||
return {
|
return {
|
||||||
@@ -120,7 +128,9 @@ async function moveNode(
|
|||||||
target_parent_token: targetParentToken,
|
target_parent_token: targetParentToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -133,7 +143,9 @@ async function renameNode(client: Lark.Client, spaceId: string, nodeToken: strin
|
|||||||
path: { space_id: spaceId, node_token: nodeToken },
|
path: { space_id: spaceId, node_token: nodeToken },
|
||||||
data: { title },
|
data: { title },
|
||||||
});
|
});
|
||||||
if (res.code !== 0) throw new Error(res.msg);
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -199,6 +211,7 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) {
|
|||||||
case "rename":
|
case "rename":
|
||||||
return json(await renameNode(client, p.space_id, p.node_token, p.title));
|
return json(await renameNode(client, p.space_id, p.node_token, p.title));
|
||||||
default:
|
default:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
||||||
return json({ error: `Unknown action: ${(p as any).action}` });
|
return json({ error: `Unknown action: ${(p as any).action}` });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user