fix excel file message parsing and dingtalk fileType reply
This commit is contained in:
parent
fa631100ba
commit
3873928b66
@ -1446,16 +1446,37 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
return codes
|
return codes
|
||||||
|
|
||||||
def _normalize_file_name(self, raw_name: str) -> str:
|
def _normalize_file_name(self, raw_name: str) -> str:
|
||||||
name = str(raw_name or "").strip().replace("\\", "_").replace("/", "_")
|
raw = str(raw_name or "").strip().replace("\\", "_").replace("/", "_")
|
||||||
name = re.sub(r"\s+", "_", name)
|
raw = re.sub(r"\s+", "_", raw)
|
||||||
name = re.sub(r"[^0-9A-Za-z._-]+", "_", name).strip("._")
|
stem, ext = os.path.splitext(raw)
|
||||||
if not name:
|
safe_stem = re.sub(r"[^0-9A-Za-z._-]+", "_", stem).strip("._-")
|
||||||
return f"upload_{int(time.time())}.xlsx"
|
safe_ext = re.sub(r"[^0-9A-Za-z.]+", "", ext).lower()
|
||||||
return name[:120]
|
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:
|
def _is_supported_excel_file(self, file_name: str) -> bool:
|
||||||
return str(file_name or "").strip().lower().endswith(".xlsx")
|
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:
|
def _is_excel_link_prompt(self, prompt: str) -> bool:
|
||||||
raw = str(prompt or "").strip().lower()
|
raw = str(prompt or "").strip().lower()
|
||||||
if not raw:
|
if not raw:
|
||||||
@ -1480,16 +1501,45 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
continue
|
continue
|
||||||
if isinstance(current, dict):
|
if isinstance(current, dict):
|
||||||
lowered = {str(k).lower(): k for k in current.keys()}
|
lowered = {str(k).lower(): k for k in current.keys()}
|
||||||
|
code = ""
|
||||||
if "downloadcode" in lowered:
|
if "downloadcode" in lowered:
|
||||||
raw_code = current.get(lowered["downloadcode"])
|
raw_code = current.get(lowered["downloadcode"])
|
||||||
code = str(raw_code or "").strip()
|
code = str(raw_code or "").strip()
|
||||||
if code:
|
|
||||||
file_name = ""
|
download_url = ""
|
||||||
for candidate in ("fileName", "filename", "file_name", "name", "title"):
|
for key_name in (
|
||||||
if candidate in current and str(current.get(candidate) or "").strip():
|
"downloadurl",
|
||||||
file_name = str(current.get(candidate)).strip()
|
"download_url",
|
||||||
break
|
"privatedownloadurl",
|
||||||
return {"download_code": code, "file_name": file_name}
|
"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():
|
for child in current.values():
|
||||||
stack.append(child)
|
stack.append(child)
|
||||||
continue
|
continue
|
||||||
@ -1509,7 +1559,16 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
payload = None
|
payload = None
|
||||||
if not payload:
|
if not payload:
|
||||||
return {}
|
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_name = payload.get("file_name") or ""
|
||||||
|
file_type = payload.get("file_type") or ""
|
||||||
if not file_name and isinstance(callback_data, dict):
|
if not file_name and isinstance(callback_data, dict):
|
||||||
for key in ("fileName", "filename", "name", "title"):
|
for key in ("fileName", "filename", "name", "title"):
|
||||||
raw = callback_data.get(key)
|
raw = callback_data.get(key)
|
||||||
@ -1517,9 +1576,21 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
file_name = str(raw).strip()
|
file_name = str(raw).strip()
|
||||||
if file_name:
|
if file_name:
|
||||||
break
|
break
|
||||||
payload["file_name"] = self._normalize_file_name(file_name or "upload.xlsx")
|
if not file_type and isinstance(callback_data, dict):
|
||||||
if not self._is_supported_excel_file(payload["file_name"]):
|
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 {}
|
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
|
return payload
|
||||||
|
|
||||||
def _guess_image_ext(self, content_type: str, source_url: str) -> str:
|
def _guess_image_ext(self, content_type: str, source_url: str) -> str:
|
||||||
@ -2200,9 +2271,11 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
raw = (proc.stdout or "").strip()
|
raw = (proc.stdout or "").strip()
|
||||||
return f"结果解析失败: {exc}.\n原始输出: {raw[:500]}"
|
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:
|
if not media_id:
|
||||||
return False
|
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 = {
|
request_headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
@ -2211,6 +2284,8 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
"msgtype": "file",
|
"msgtype": "file",
|
||||||
"file": {
|
"file": {
|
||||||
"mediaId": media_id,
|
"mediaId": media_id,
|
||||||
|
"fileName": safe_name,
|
||||||
|
"fileType": file_ext,
|
||||||
},
|
},
|
||||||
"at": {
|
"at": {
|
||||||
"atUserIds": [msg.sender_staff_id] if msg.sender_staff_id else [],
|
"atUserIds": [msg.sender_staff_id] if msg.sender_staff_id else [],
|
||||||
@ -2337,15 +2412,17 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
return f"转换脚本不存在:{script_path}", False
|
return f"转换脚本不存在:{script_path}", False
|
||||||
|
|
||||||
download_code = str(file_payload.get("download_code") or "").strip()
|
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")
|
file_name = self._normalize_file_name(file_payload.get("file_name") or "upload.xlsx")
|
||||||
if not download_code:
|
if not download_code and not download_url:
|
||||||
return "未获取到文件下载码,请重试上传。", False
|
return "未获取到文件下载地址,请重试上传。", False
|
||||||
if not self._is_supported_excel_file(file_name):
|
if not self._is_supported_excel_file(file_name):
|
||||||
return "当前仅支持 .xlsx 文件,请上传Excel后重试。", False
|
return "当前仅支持 .xlsx 文件,请上传Excel后重试。", False
|
||||||
|
|
||||||
download_url = self.get_image_download_url(download_code)
|
|
||||||
if not download_url:
|
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:
|
with tempfile.TemporaryDirectory(prefix="dingtalk_excel_link_") as tmpdir:
|
||||||
input_path = os.path.join(tmpdir, file_name)
|
input_path = os.path.join(tmpdir, file_name)
|
||||||
@ -2437,7 +2514,7 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.logger.exception("upload converted excel failed")
|
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:
|
if not sent:
|
||||||
s3_url = self._upload_excel_output_to_s3(output_path, output_name)
|
s3_url = self._upload_excel_output_to_s3(output_path, output_name)
|
||||||
if s3_url:
|
if s3_url:
|
||||||
@ -2588,6 +2665,15 @@ class OpenClawDingTalkBot(ChatbotHandler):
|
|||||||
bool(incoming_image_codes),
|
bool(incoming_image_codes),
|
||||||
len(text or ""),
|
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:
|
if not text and not incoming_image_codes and not has_excel_file:
|
||||||
return AckMessage.STATUS_OK, "ignored"
|
return AckMessage.STATUS_OK, "ignored"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user