- 调整依赖导入顺序与清理冗余空行 - 修复setup wizard步骤存储逻辑 - 新增消息编辑/转发标记、卡片控件支持 - 完善目标校验、媒体上传、配对存储功能 - 优化流式响应与错误处理逻辑 - 增强SSRF防护与配置灵活性 - 新增/扩展内置命令与卡片处理能力 - 调整媒体大小限制与类型参数
108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
_CHANNEL_PREFIXES = ("googlechat:", "gchat:", "google-chat:")
|
|
|
|
_VALID_GC_RESOURCE_PREFIXES = frozenset({"spaces/", "users/"})
|
|
_MESSAGES_SUFFIX_RE = re.compile(r"/messages/[^/]+$")
|
|
|
|
|
|
def strip_message_suffix(target: str) -> tuple[str, bool]:
|
|
stripped = _MESSAGES_SUFFIX_RE.sub("", target)
|
|
return stripped, stripped != target
|
|
|
|
|
|
def detect_deprecated_target(target: str) -> str | None:
|
|
cleaned = _strip_channel_prefix(target)
|
|
for prefix in _VALID_GC_RESOURCE_PREFIXES:
|
|
if cleaned.startswith(prefix):
|
|
return None
|
|
return f"Target '{target}' does not use a valid Google Chat resource prefix (spaces/ or users/)"
|
|
|
|
|
|
def normalize_googlechat_target(target: str) -> dict[str, Any]:
|
|
if not target:
|
|
return {"raw": target, "type": "unknown"}
|
|
|
|
target = _strip_channel_prefix(target)
|
|
|
|
if "@" in target and not target.startswith(("spaces/", "users/")):
|
|
target = _convert_email_to_user(target)
|
|
|
|
if target.startswith("spaces/"):
|
|
parts = target.removeprefix("spaces/").split("/")
|
|
space_id = parts[0]
|
|
thread_key = parts[2] if len(parts) > 2 and parts[1] == "threads" else None
|
|
return {
|
|
"raw": target,
|
|
"type": "thread" if thread_key else "space",
|
|
"space_id": space_id,
|
|
"space_name": f"spaces/{space_id}",
|
|
"thread_key": thread_key,
|
|
"full_target": target,
|
|
}
|
|
|
|
if target.startswith("users/"):
|
|
user_id = target.removeprefix("users/")
|
|
cleaned = user_id.removeprefix("user:")
|
|
return {
|
|
"raw": target,
|
|
"type": "user",
|
|
"user_id": cleaned,
|
|
"user_name": f"users/{cleaned}",
|
|
"full_target": target,
|
|
}
|
|
|
|
if target.startswith("user:"):
|
|
user_id = target.removeprefix("user:")
|
|
return {
|
|
"raw": target,
|
|
"type": "user",
|
|
"user_id": user_id,
|
|
"user_name": f"users/{user_id}",
|
|
"full_target": f"users/{user_id}",
|
|
}
|
|
|
|
return {"raw": target, "type": "unknown"}
|
|
|
|
|
|
def is_googlechat_user_target(target: str) -> bool:
|
|
target = _strip_channel_prefix(target)
|
|
return target.startswith("users/") or target.startswith("user:")
|
|
|
|
|
|
def is_googlechat_space_target(target: str) -> bool:
|
|
target = _strip_channel_prefix(target)
|
|
return target.startswith("spaces/")
|
|
|
|
|
|
def resolve_targets(inputs: list[str], kind: str | None = None) -> list[dict[str, Any]]:
|
|
results: list[dict[str, Any]] = []
|
|
|
|
for target in inputs:
|
|
normalized = normalize_googlechat_target(target)
|
|
target_type = normalized.get("type", "unknown")
|
|
|
|
if kind and target_type != kind:
|
|
continue
|
|
|
|
results.append(normalized)
|
|
|
|
return results
|
|
|
|
|
|
def _strip_channel_prefix(target: str) -> str:
|
|
for prefix in _CHANNEL_PREFIXES:
|
|
if target.lower().startswith(prefix):
|
|
return target[len(prefix) :]
|
|
return target
|
|
|
|
|
|
def _convert_email_to_user(email: str) -> str:
|
|
email_match = re.match(r"^[\w.+-]+@[\w-]+\.[\w.-]+$", email)
|
|
if email_match:
|
|
return f"users/{email}"
|
|
return f"users/{email}"
|