Files
soul-yongping/scripts/send_feishu_text_and_images.py

139 lines
4.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
向开发群 webhook 发送一条长文本 + 若干本地 PNG先上传飞书再发 image_key
依赖 send_chapter_poster_to_feishu.py 相同 scripts/.env.feishu FEISHU_APP_ID / FEISHU_APP_SECRET
用法:
python3 send_feishu_text_and_images.py --text-file recap.txt \\
--images a.png b.png
python3 send_feishu_text_and_images.py -t "单行文本" --images x.png
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
try:
import requests
except ImportError:
print("pip install requests", file=sys.stderr)
sys.exit(1)
DEFAULT_WEBHOOK = os.environ.get(
"FEISHU_DEV_GROUP_WEBHOOK",
"https://open.feishu.cn/open-apis/bot/v2/hook/c558df98-e13a-419f-a3c0-7e428d15f494",
)
def load_env_feishu():
p = SCRIPT_DIR / ".env.feishu"
if not p.is_file():
return
for line in p.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
def tenant_token() -> str | None:
load_env_feishu()
app_id = os.environ.get("FEISHU_APP_ID", "")
sec = os.environ.get("FEISHU_APP_SECRET", "")
if not app_id or not sec:
print("缺少 FEISHU_APP_ID / FEISHU_APP_SECRET.env.feishu", file=sys.stderr)
return None
r = requests.post(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
json={"app_id": app_id, "app_secret": sec},
timeout=15,
)
data = r.json() or {}
if data.get("code") != 0:
print("token 失败:", data, file=sys.stderr)
return None
return data.get("tenant_access_token")
def send_text(webhook: str, text: str) -> bool:
r = requests.post(webhook, json={"msg_type": "text", "content": {"text": text}}, timeout=15)
d = r.json() or {}
if d.get("code") != 0:
print("文本发送失败:", d, file=sys.stderr)
return False
return True
def upload_png(token: str, path: Path) -> str | None:
url = "https://open.feishu.cn/open-apis/im/v1/images"
headers = {"Authorization": f"Bearer {token}"}
with path.open("rb") as f:
r = requests.post(
url,
headers=headers,
files={"image": (path.name, f, "image/png")},
data={"image_type": "message"},
timeout=60,
)
out = r.json() or {}
if out.get("code") != 0:
print("上传失败", path, out, file=sys.stderr)
return None
return (out.get("data") or {}).get("image_key")
def send_image(webhook: str, image_key: str) -> bool:
r = requests.post(
webhook,
json={"msg_type": "image", "content": {"image_key": image_key}},
timeout=15,
)
d = r.json() or {}
if d.get("code") != 0:
print("图片消息失败:", d, file=sys.stderr)
return False
return True
def main():
ap = argparse.ArgumentParser()
ap.add_argument("-t", "--text", default="", help="直接传入文本")
ap.add_argument("--text-file", type=Path, help="从文件读文本utf-8")
ap.add_argument("--webhook", "-w", default=DEFAULT_WEBHOOK)
ap.add_argument("--images", "-i", nargs="*", default=[], help="PNG 路径列表")
args = ap.parse_args()
body = args.text.strip()
if args.text_file:
body = args.text_file.read_text(encoding="utf-8").strip()
if not body:
ap.error("需要 -t 或 --text-file")
if not send_text(args.webhook, body[:20000]):
sys.exit(1)
print("已发文本")
if not args.images:
return
tok = tenant_token()
if not tok:
sys.exit(1)
for p in args.images:
path = Path(p).expanduser().resolve()
if not path.is_file():
print("跳过(不存在):", path, file=sys.stderr)
continue
key = upload_png(tok, path)
if key and send_image(args.webhook, key):
print("已发图:", path.name)
if __name__ == "__main__":
main()