fix excel file message parsing and dingtalk fileType reply

This commit is contained in:
Mayorz 2026-03-04 17:08:55 +08:00
parent fa631100ba
commit 3873928b66

View File

@ -1446,16 +1446,37 @@ class OpenClawDingTalkBot(ChatbotHandler):
return codes
def _normalize_file_name(self, raw_name: str) -> str:
name = str(raw_name or "").strip().replace("\\", "_").replace("/", "_")
name = re.sub(r"\s+", "_", name)
name = re.sub(r"[^0-9A-Za-z._-]+", "_", name).strip("._")
if not name:
return f"upload_{int(time.time())}.xlsx"
return name[:120]
raw = str(raw_name or "").strip().replace("\\", "_").replace("/", "_")
raw = re.sub(r"\s+", "_", raw)
stem, ext = os.path.splitext(raw)
safe_stem = re.sub(r"[^0-9A-Za-z._-]+", "_", stem).strip("._-")
safe_ext = re.sub(r"[^0-9A-Za-z.]+", "", ext).lower()
if safe_ext and not safe_ext.startswith("."):
safe_ext = f".{safe_ext}"
if not safe_stem:
safe_stem = f"upload_{int(time.time())}"
normalized = f"{safe_stem}{safe_ext}" if safe_ext else safe_stem
return normalized[:120]
def _is_supported_excel_file(self, file_name: str) -> bool:
return str(file_name or "").strip().lower().endswith(".xlsx")
def _is_supported_excel_payload(self, file_name: str, file_type: str) -> bool:
if self._is_supported_excel_file(file_name):
return True
normalized = str(file_type or "").strip().lower()
if not normalized:
return False
normalized = normalized.split(";")[0].strip()
if normalized in {
"xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}:
return True
if "spreadsheetml.sheet" in normalized:
return True
return False
def _is_excel_link_prompt(self, prompt: str) -> bool:
raw = str(prompt or "").strip().lower()
if not raw:
@ -1480,16 +1501,45 @@ class OpenClawDingTalkBot(ChatbotHandler):
continue
if isinstance(current, dict):
lowered = {str(k).lower(): k for k in current.keys()}
code = ""
if "downloadcode" in lowered:
raw_code = current.get(lowered["downloadcode"])
code = str(raw_code or "").strip()
if code:
file_name = ""
for candidate in ("fileName", "filename", "file_name", "name", "title"):
if candidate in current and str(current.get(candidate) or "").strip():
file_name = str(current.get(candidate)).strip()
break
return {"download_code": code, "file_name": file_name}
download_url = ""
for key_name in (
"downloadurl",
"download_url",
"privatedownloadurl",
"private_download_url",
"privateurl",
):
if key_name in lowered:
raw_url = current.get(lowered[key_name])
candidate_url = str(raw_url or "").strip()
if candidate_url.lower().startswith(("http://", "https://")):
download_url = candidate_url
break
file_name = ""
for candidate in ("fileName", "filename", "file_name", "name", "title"):
if candidate in current and str(current.get(candidate) or "").strip():
file_name = str(current.get(candidate)).strip()
break
file_type = ""
for candidate in ("fileType", "filetype", "mimeType", "mimetype", "suffix", "ext", "extension"):
if candidate in current and str(current.get(candidate) or "").strip():
file_type = str(current.get(candidate)).strip()
break
if code or download_url:
return {
"download_code": code,
"download_url": download_url,
"file_name": file_name,
"file_type": file_type,
}
for child in current.values():
stack.append(child)
continue
@ -1509,7 +1559,16 @@ class OpenClawDingTalkBot(ChatbotHandler):
payload = None
if not payload:
return {}
download_code = str(payload.get("download_code") or "").strip()
download_url = str(payload.get("download_url") or "").strip()
if download_url and not download_url.lower().startswith(("http://", "https://")):
download_url = ""
if not download_code and not download_url:
return {}
file_name = payload.get("file_name") or ""
file_type = payload.get("file_type") or ""
if not file_name and isinstance(callback_data, dict):
for key in ("fileName", "filename", "name", "title"):
raw = callback_data.get(key)
@ -1517,9 +1576,21 @@ class OpenClawDingTalkBot(ChatbotHandler):
file_name = str(raw).strip()
if file_name:
break
payload["file_name"] = self._normalize_file_name(file_name or "upload.xlsx")
if not self._is_supported_excel_file(payload["file_name"]):
if not file_type and isinstance(callback_data, dict):
for key in ("fileType", "filetype", "mimeType", "mimetype", "suffix", "ext", "extension"):
raw = callback_data.get(key)
if raw:
file_type = str(raw).strip()
if file_type:
break
normalized_file_name = self._normalize_file_name(file_name or "upload.xlsx")
normalized_file_type = str(file_type or "").strip().lower()
if not self._is_supported_excel_payload(normalized_file_name, normalized_file_type):
return {}
payload["download_code"] = download_code
payload["download_url"] = download_url
payload["file_name"] = normalized_file_name
payload["file_type"] = normalized_file_type
return payload
def _guess_image_ext(self, content_type: str, source_url: str) -> str:
@ -2200,9 +2271,11 @@ class OpenClawDingTalkBot(ChatbotHandler):
raw = (proc.stdout or "").strip()
return f"结果解析失败: {exc}.\n原始输出: {raw[:500]}"
def _reply_file_by_media_id(self, msg: ChatbotMessage, media_id: str) -> bool:
def _reply_file_by_media_id(self, msg: ChatbotMessage, media_id: str, file_name: str = "") -> bool:
if not media_id:
return False
safe_name = self._normalize_file_name(file_name or f"excel_{int(time.time())}.xlsx")
file_ext = os.path.splitext(safe_name)[1].lower().lstrip(".") or "file"
request_headers = {
"Content-Type": "application/json",
"Accept": "*/*",
@ -2211,6 +2284,8 @@ class OpenClawDingTalkBot(ChatbotHandler):
"msgtype": "file",
"file": {
"mediaId": media_id,
"fileName": safe_name,
"fileType": file_ext,
},
"at": {
"atUserIds": [msg.sender_staff_id] if msg.sender_staff_id else [],
@ -2337,15 +2412,17 @@ class OpenClawDingTalkBot(ChatbotHandler):
return f"转换脚本不存在:{script_path}", False
download_code = str(file_payload.get("download_code") or "").strip()
download_url = str(file_payload.get("download_url") or "").strip()
file_name = self._normalize_file_name(file_payload.get("file_name") or "upload.xlsx")
if not download_code:
return "未获取到文件下载码,请重试上传。", False
if not download_code and not download_url:
return "未获取到文件下载地址,请重试上传。", False
if not self._is_supported_excel_file(file_name):
return "当前仅支持 .xlsx 文件,请上传Excel后重试。", False
download_url = self.get_image_download_url(download_code)
if not download_url:
return "文件下载地址获取失败,请重试。", False
download_url = self.get_image_download_url(download_code)
if not download_url:
return "文件下载地址获取失败,请重试。", False
with tempfile.TemporaryDirectory(prefix="dingtalk_excel_link_") as tmpdir:
input_path = os.path.join(tmpdir, file_name)
@ -2437,7 +2514,7 @@ class OpenClawDingTalkBot(ChatbotHandler):
except Exception as exc:
self.logger.exception("upload converted excel failed")
sent = self._reply_file_by_media_id(msg, media_id) if media_id else False
sent = self._reply_file_by_media_id(msg, media_id, output_name) if media_id else False
if not sent:
s3_url = self._upload_excel_output_to_s3(output_path, output_name)
if s3_url:
@ -2588,6 +2665,15 @@ class OpenClawDingTalkBot(ChatbotHandler):
bool(incoming_image_codes),
len(text or ""),
)
if str(getattr(msg, "message_type", "")).lower() == "file" and not has_excel_file:
callback_keys = []
if isinstance(callback.data, dict):
callback_keys = list(callback.data.keys())[:15]
self.logger.warning(
"file message without excel payload: callback_keys=%s extensions_keys=%s",
callback_keys,
list((getattr(msg, "extensions", {}) or {}).keys())[:15],
)
if not text and not incoming_image_codes and not has_excel_file:
return AckMessage.STATUS_OK, "ignored"