fix(feishu): ensure access token is initialized before fetching bot open_id

The lark-oapi client requires token types to be explicitly configured
so that the SDK can obtain and attach the tenant_access_token to raw
requests. Without this, `_fetch_bot_open_id()` would fail with
"Missing access token for authorization" because the token had not
been provisioned at the time of the call.
This commit is contained in:
wudongxue 2026-03-31 12:52:32 +08:00 committed by Xubin Ren
parent 0291d1f716
commit b3294f79aa

View File

@ -1,6 +1,7 @@
"""Feishu/Lark channel implementation using lark-oapi SDK with WebSocket long connection.""" """Feishu/Lark channel implementation using lark-oapi SDK with WebSocket long connection."""
import asyncio import asyncio
import importlib.util
import json import json
import os import os
import re import re
@ -11,18 +12,15 @@ from collections import OrderedDict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Literal from typing import Any, Literal
from lark_oapi.api.im.v1.model import P2ImMessageReceiveV1, MentionEvent from lark_oapi.api.im.v1.model import MentionEvent, P2ImMessageReceiveV1
from loguru import logger from loguru import logger
from pydantic import Field
from nanobot.bus.events import OutboundMessage from nanobot.bus.events import OutboundMessage
from nanobot.bus.queue import MessageBus from nanobot.bus.queue import MessageBus
from nanobot.channels.base import BaseChannel from nanobot.channels.base import BaseChannel
from nanobot.config.paths import get_media_dir from nanobot.config.paths import get_media_dir
from nanobot.config.schema import Base from nanobot.config.schema import Base
from pydantic import Field
import importlib.util
FEISHU_AVAILABLE = importlib.util.find_spec("lark_oapi") is not None FEISHU_AVAILABLE = importlib.util.find_spec("lark_oapi") is not None
@ -292,11 +290,13 @@ class FeishuChannel(BaseChannel):
return FeishuConfig().model_dump(by_alias=True) return FeishuConfig().model_dump(by_alias=True)
def __init__(self, config: Any, bus: MessageBus): def __init__(self, config: Any, bus: MessageBus):
import lark_oapi as lark
if isinstance(config, dict): if isinstance(config, dict):
config = FeishuConfig.model_validate(config) config = FeishuConfig.model_validate(config)
super().__init__(config, bus) super().__init__(config, bus)
self.config: FeishuConfig = config self.config: FeishuConfig = config
self._client: Any = None self._client: lark.Client = None
self._ws_client: Any = None self._ws_client: Any = None
self._ws_thread: threading.Thread | None = None self._ws_thread: threading.Thread | None = None
self._processed_message_ids: OrderedDict[str, None] = OrderedDict() # Ordered dedup cache self._processed_message_ids: OrderedDict[str, None] = OrderedDict() # Ordered dedup cache
@ -340,6 +340,9 @@ class FeishuChannel(BaseChannel):
builder = self._register_optional_event( builder = self._register_optional_event(
builder, "register_p2_im_message_reaction_created_v1", self._on_reaction_created builder, "register_p2_im_message_reaction_created_v1", self._on_reaction_created
) )
builder = self._register_optional_event(
builder, "register_p2_im_message_reaction_deleted_v1", self._on_reaction_deleted
)
builder = self._register_optional_event( builder = self._register_optional_event(
builder, "register_p2_im_message_message_read_v1", self._on_message_read builder, "register_p2_im_message_message_read_v1", self._on_message_read
) )
@ -365,6 +368,7 @@ class FeishuChannel(BaseChannel):
# "This event loop is already running" errors. # "This event loop is already running" errors.
def run_ws(): def run_ws():
import time import time
import lark_oapi.ws.client as _lark_ws_client import lark_oapi.ws.client as _lark_ws_client
ws_loop = asyncio.new_event_loop() ws_loop = asyncio.new_event_loop()
@ -418,9 +422,10 @@ class FeishuChannel(BaseChannel):
import lark_oapi as lark import lark_oapi as lark
request = ( request = (
lark.RawRequest.builder() lark.BaseRequest.builder()
.http_method(lark.HttpMethod.GET) .http_method(lark.HttpMethod.GET)
.uri("/open-apis/bot/v3/info") .uri("/open-apis/bot/v3/info")
.token_types({lark.AccessTokenType.APP})
.build() .build()
) )
response = self._client.request(request) response = self._client.request(request)
@ -455,12 +460,12 @@ class FeishuChannel(BaseChannel):
if not key or key not in text: if not key or key not in text:
continue continue
userID = mention.id or None user_id_obj = mention.id or None
if not userID: if not user_id_obj:
continue continue
open_id = userID.open_id open_id = user_id_obj.open_id
user_id = userID.user_id user_id = user_id_obj.user_id
name = mention.name or key name = mention.name or key
# Format: @姓名 (open_id, user_id: xxx) # Format: @姓名 (open_id, user_id: xxx)
@ -1576,7 +1581,6 @@ class FeishuChannel(BaseChannel):
"thread_id": thread_id, "thread_id": thread_id,
}, },
) )
await self._del_reaction(message_id, reaction_id)
except Exception as e: except Exception as e:
logger.error("Error processing Feishu message: {}", e) logger.error("Error processing Feishu message: {}", e)
@ -1585,6 +1589,10 @@ class FeishuChannel(BaseChannel):
"""Ignore reaction events so they do not generate SDK noise.""" """Ignore reaction events so they do not generate SDK noise."""
pass pass
def _on_reaction_deleted(self, data: Any) -> None:
"""Ignore reaction deleted events so they do not generate SDK noise."""
pass
def _on_message_read(self, data: Any) -> None: def _on_message_read(self, data: Any) -> None:
"""Ignore read events so they do not generate SDK noise.""" """Ignore read events so they do not generate SDK noise."""
pass pass