chattercatcher 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import fs13 from "fs/promises";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "chattercatcher",
11
- version: "0.1.11",
11
+ version: "0.1.13",
12
12
  description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
13
13
  type: "module",
14
14
  main: "dist/index.js",
@@ -88,6 +88,7 @@ var appConfigSchema = z.object({
88
88
  feishu: z.object({
89
89
  domain: z.enum(["feishu", "lark"]).default("feishu"),
90
90
  appId: z.string().default(""),
91
+ botOpenId: z.string().default(""),
91
92
  groupPolicy: z.enum(["open", "allowlist", "disabled"]).default("open"),
92
93
  requireMention: z.boolean().default(true)
93
94
  }),
@@ -1944,6 +1945,80 @@ async function restoreLocalData(input2) {
1944
1945
  };
1945
1946
  }
1946
1947
 
1948
+ // src/feishu/bot-info.ts
1949
+ function getOpenApiBaseUrl(domain) {
1950
+ return domain === "lark" ? "https://open.larksuite.com/open-apis" : "https://open.feishu.cn/open-apis";
1951
+ }
1952
+ async function readFeishuJson(response) {
1953
+ if (!response.ok) {
1954
+ throw new Error(`\u98DE\u4E66\u63A5\u53E3\u8BF7\u6C42\u5931\u8D25\uFF1AHTTP ${response.status}`);
1955
+ }
1956
+ return response.json();
1957
+ }
1958
+ function assertFeishuSuccess(payload, fallbackMessage) {
1959
+ if (!payload || typeof payload !== "object") {
1960
+ throw new Error(fallbackMessage);
1961
+ }
1962
+ const code = payload.code;
1963
+ if (code !== 0) {
1964
+ const message = payload.msg;
1965
+ throw new Error(typeof message === "string" ? message : fallbackMessage);
1966
+ }
1967
+ }
1968
+ async function resolveFeishuBotOpenId(config, secrets, options = {}) {
1969
+ if (!config.feishu.appId || !secrets.feishu.appSecret) {
1970
+ throw new Error("\u98DE\u4E66 App ID \u6216 App Secret \u672A\u914D\u7F6E\u3002");
1971
+ }
1972
+ const fetchImpl = options.fetch ?? fetch;
1973
+ const baseUrl = getOpenApiBaseUrl(config.feishu.domain);
1974
+ const tokenPayload = await readFeishuJson(
1975
+ await fetchImpl(`${baseUrl}/auth/v3/tenant_access_token/internal`, {
1976
+ method: "POST",
1977
+ headers: { "content-type": "application/json" },
1978
+ body: JSON.stringify({
1979
+ app_id: config.feishu.appId,
1980
+ app_secret: secrets.feishu.appSecret
1981
+ })
1982
+ })
1983
+ );
1984
+ assertFeishuSuccess(tokenPayload, "\u83B7\u53D6\u98DE\u4E66 tenant_access_token \u5931\u8D25\u3002");
1985
+ const tenantAccessToken = tokenPayload.tenant_access_token;
1986
+ if (typeof tenantAccessToken !== "string" || !tenantAccessToken) {
1987
+ throw new Error("\u98DE\u4E66 tenant_access_token \u54CD\u5E94\u7F3A\u5C11 token\u3002");
1988
+ }
1989
+ const botInfoPayload = await readFeishuJson(
1990
+ await fetchImpl(`${baseUrl}/bot/v3/info`, {
1991
+ method: "GET",
1992
+ headers: { Authorization: `Bearer ${tenantAccessToken}` }
1993
+ })
1994
+ );
1995
+ assertFeishuSuccess(botInfoPayload, "\u83B7\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u4FE1\u606F\u5931\u8D25\u3002");
1996
+ const bot = botInfoPayload.bot;
1997
+ if (!bot || typeof bot !== "object") {
1998
+ throw new Error("\u98DE\u4E66\u673A\u5668\u4EBA\u4FE1\u606F\u54CD\u5E94\u7F3A\u5C11 bot\u3002");
1999
+ }
2000
+ const openId = bot.open_id;
2001
+ if (typeof openId !== "string" || !openId) {
2002
+ throw new Error("\u98DE\u4E66\u673A\u5668\u4EBA\u4FE1\u606F\u54CD\u5E94\u7F3A\u5C11 open_id\u3002");
2003
+ }
2004
+ return openId;
2005
+ }
2006
+ async function ensureFeishuBotOpenId(config, secrets, options = {}) {
2007
+ if (config.feishu.botOpenId) {
2008
+ return config.feishu.botOpenId;
2009
+ }
2010
+ const openId = await resolveFeishuBotOpenId(config, secrets, options);
2011
+ const previousOpenId = config.feishu.botOpenId;
2012
+ config.feishu.botOpenId = openId;
2013
+ try {
2014
+ await options.onSave?.();
2015
+ } catch (error) {
2016
+ config.feishu.botOpenId = previousOpenId;
2017
+ throw error;
2018
+ }
2019
+ return openId;
2020
+ }
2021
+
1947
2022
  // src/feishu/gateway.ts
1948
2023
  import * as lark2 from "@larksuiteoapi/node-sdk";
1949
2024
 
@@ -2106,12 +2181,22 @@ function stripMentions(text, mentions) {
2106
2181
  }
2107
2182
  return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
2108
2183
  }
2109
- function isFeishuMessageAddressedToBot(payload) {
2184
+ function isMentionForBot(mention, config) {
2185
+ if (!config.feishu.botOpenId) {
2186
+ return false;
2187
+ }
2188
+ return mention.id?.open_id === config.feishu.botOpenId;
2189
+ }
2190
+ function getBotMentions(payload, config) {
2191
+ const message = payload.event?.message;
2192
+ return (message?.mentions ?? []).filter((mention) => isMentionForBot(mention, config));
2193
+ }
2194
+ function isFeishuMessageAddressedToBot(payload, config) {
2110
2195
  const message = payload.event?.message;
2111
2196
  if (!message || message.message_type !== "text") {
2112
2197
  return false;
2113
2198
  }
2114
- return (message.mentions ?? []).length > 0;
2199
+ return getBotMentions(payload, config).length > 0;
2115
2200
  }
2116
2201
  function getFeishuQuestionDecision(payload, config) {
2117
2202
  const message = payload.event?.message;
@@ -2121,9 +2206,9 @@ function getFeishuQuestionDecision(payload, config) {
2121
2206
  reason: "\u4E0D\u662F\u53EF\u56DE\u7B54\u7684\u6587\u672C\u6D88\u606F\u3002"
2122
2207
  };
2123
2208
  }
2124
- const mentions = message.mentions ?? [];
2209
+ const mentions = getBotMentions(payload, config);
2125
2210
  const text = parseTextContent(message.content);
2126
- const hasMention = isFeishuMessageAddressedToBot(payload);
2211
+ const hasMention = isFeishuMessageAddressedToBot(payload, config);
2127
2212
  if (config.feishu.requireMention && !hasMention) {
2128
2213
  return {
2129
2214
  shouldAnswer: false,
@@ -2300,7 +2385,7 @@ function createFeishuEventDispatcher(options) {
2300
2385
  return new lark2.EventDispatcher({}).register({
2301
2386
  "im.message.receive_v1": async (data2) => {
2302
2387
  const payload = { event: data2 };
2303
- if (options.questionHandler && isFeishuMessageAddressedToBot(payload)) {
2388
+ if (options.questionHandler && isFeishuMessageAddressedToBot(payload, options.config)) {
2304
2389
  const platformMessageId = data2?.message?.message_id;
2305
2390
  if (platformMessageId && answeredMessageIds.has(platformMessageId)) {
2306
2391
  console.log("\u98DE\u4E66\u63D0\u95EE\u91CD\u590D\u6295\u9012\uFF1A\u5DF2\u8DF3\u8FC7\u56DE\u7B54\u3002");
@@ -3584,6 +3669,7 @@ async function promptForConfiguration(config, secrets) {
3584
3669
  secrets.feishu.appSecret,
3585
3670
  await password({ message: secrets.feishu.appSecret ? "\u98DE\u4E66 App Secret\uFF08\u7559\u7A7A\u4FDD\u7559\uFF09" : "\u98DE\u4E66 App Secret", mask: "*" })
3586
3671
  );
3672
+ await tryEnsureFeishuBotOpenId(config, secrets);
3587
3673
  config.llm.baseUrl = await input({ message: "LLM Base URL\uFF08OpenAI-compatible\uFF09", default: config.llm.baseUrl });
3588
3674
  secrets.llm.apiKey = applySecretInput(
3589
3675
  secrets.llm.apiKey,
@@ -3611,10 +3697,21 @@ async function promptForConfiguration(config, secrets) {
3611
3697
  config.embedding.dimension = dimension ?? null;
3612
3698
  config.web.port = await number({ message: "Web UI \u7AEF\u53E3", default: config.web.port, required: true }) ?? config.web.port;
3613
3699
  config.feishu.requireMention = await confirm({
3614
- message: "\u7FA4\u804A\u56DE\u7B54\u662F\u5426\u8981\u6C42 @ChatterCatcher\uFF1F",
3700
+ message: "\u7FA4\u804A\u56DE\u7B54\u662F\u5426\u8981\u6C42 @ \u673A\u5668\u4EBA\uFF1F",
3615
3701
  default: config.feishu.requireMention
3616
3702
  });
3617
3703
  }
3704
+ async function tryEnsureFeishuBotOpenId(config, secrets) {
3705
+ if (config.feishu.botOpenId || !config.feishu.appId || !secrets.feishu.appSecret) {
3706
+ return;
3707
+ }
3708
+ try {
3709
+ const openId = await ensureFeishuBotOpenId(config, secrets, { onSave: () => saveConfig(config) });
3710
+ console.log(`\u5DF2\u81EA\u52A8\u83B7\u53D6\u98DE\u4E66\u673A\u5668\u4EBA Open ID\uFF1A${openId}`);
3711
+ } catch (error) {
3712
+ console.log(`\u6682\u65F6\u65E0\u6CD5\u81EA\u52A8\u83B7\u53D6\u98DE\u4E66\u673A\u5668\u4EBA Open ID\uFF1A${error instanceof Error ? error.message : String(error)}`);
3713
+ }
3714
+ }
3618
3715
  function printSettings(config, secrets) {
3619
3716
  console.log(JSON.stringify(
3620
3717
  {
@@ -3713,6 +3810,7 @@ async function startGatewayForegroundCommand() {
3713
3810
  await startWebServer(config);
3714
3811
  return;
3715
3812
  }
3813
+ await tryEnsureFeishuBotOpenId(config, secrets);
3716
3814
  writeGatewayPidRecord(void 0, {
3717
3815
  ...pidRecordBase,
3718
3816
  mode: "gateway"
@@ -3767,6 +3865,7 @@ async function startGatewayCommand(options = {}) {
3767
3865
  }
3768
3866
  const config = await loadConfig();
3769
3867
  const secrets = await loadSecrets();
3868
+ await tryEnsureFeishuBotOpenId(config, secrets);
3770
3869
  const result = await startDetachedGateway({ config, secrets });
3771
3870
  console.log(result.message);
3772
3871
  if (result.pid) {