72 lines
1.9 KiB
Python
72 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import unicodedata
|
|
|
|
|
|
def normalize_open_id(open_id: str) -> str:
|
|
return open_id.strip().lower() if open_id else ""
|
|
|
|
|
|
def normalize_chat_id(chat_id: str) -> str:
|
|
return chat_id.strip() if chat_id else ""
|
|
|
|
|
|
def safe_filename(name: str) -> str:
|
|
name = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", name)
|
|
name = name.replace('"', "'").replace("\\", "_")
|
|
return name.strip() or "file"
|
|
|
|
|
|
def recover_latin1_filename(value: str) -> str:
|
|
try:
|
|
return value.encode("latin-1").decode("utf-8")
|
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
return value
|
|
|
|
|
|
def build_sequential_key(chat_id: str, account_id: str) -> str:
|
|
return f"{account_id}:{chat_id}"
|
|
|
|
|
|
def parse_feishu_allow_entry(entry: str) -> tuple[str, str]:
|
|
entry = entry.strip()
|
|
if entry == "*":
|
|
return ("wildcard", "*")
|
|
if ":" in entry:
|
|
prefix, value = entry.split(":", 1)
|
|
return (prefix.strip().lower(), value.strip())
|
|
if entry.startswith("oc_"):
|
|
return ("chat", entry)
|
|
if entry.startswith("ou_"):
|
|
return ("user", entry)
|
|
return ("unknown", entry)
|
|
|
|
|
|
def is_broadcast_mention(mention_key: str) -> bool:
|
|
return mention_key in ("@all", "@_all", "@everyone")
|
|
|
|
|
|
def extract_image_keys(text: str) -> list[str]:
|
|
return re.findall(r"img_v2_[a-zA-Z0-9]+", text)
|
|
|
|
|
|
def sanitize_markdown_escape(text: str) -> str:
|
|
special_chars = r"\*_{}[]()#+-!|>~"
|
|
result = []
|
|
for ch in text:
|
|
if ch in special_chars:
|
|
result.append("\\" + ch)
|
|
else:
|
|
result.append(ch)
|
|
return "".join(result)
|
|
|
|
|
|
def ellipsize(text: str, max_len: int = 100) -> str:
|
|
if len(text) <= max_len:
|
|
return text
|
|
return text[: max_len - 1] + "…"
|
|
|
|
|
|
def strip_control_chars(text: str) -> str:
|
|
return "".join(ch for ch in text if unicodedata.category(ch)[0] != "C" or ch in ("\n", "\r", "\t")) |