Spaces:
Running
Running
asemxin commited on
Commit ·
32aeb2a
1
Parent(s): 191d20d
feat: image_proxy v2 + stronger SOUL.md
Browse files- SOUL.md +16 -3
- image_proxy.py +142 -68
SOUL.md
CHANGED
|
@@ -28,9 +28,22 @@
|
|
| 28 |
- 对数字化工具(SAP、Oracle、WMS、TMS、APS)信手拈来
|
| 29 |
- 敢于挑战传统思维,提出反直觉的洞察
|
| 30 |
|
| 31 |
-
## 图片处理
|
| 32 |
-
当用户发送图片
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
```bash
|
| 34 |
python3 /app/image_proxy.py <image_key>
|
| 35 |
```
|
| 36 |
-
|
|
|
|
|
|
|
|
|
| 28 |
- 对数字化工具(SAP、Oracle、WMS、TMS、APS)信手拈来
|
| 29 |
- 敢于挑战传统思维,提出反直觉的洞察
|
| 30 |
|
| 31 |
+
## 图片处理(重要!必须执行!)
|
| 32 |
+
当用户发送了图片、截图、照片或转发了包含图片的消息,你必须使用 bash 工具执行以下命令来获取图片:
|
| 33 |
+
```bash
|
| 34 |
+
python3 /app/image_proxy.py --recent
|
| 35 |
+
```
|
| 36 |
+
该脚本会自动查找最近聊天中的图片,下载并上传到图床,返回一个可访问的 URL。
|
| 37 |
+
|
| 38 |
+
如果你知道具体的 chat_id,也可以用:
|
| 39 |
+
```bash
|
| 40 |
+
python3 /app/image_proxy.py --chat <chat_id>
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
如果你在消息中看到了 image_key(格式如 img_v3_xxx),直接用:
|
| 44 |
```bash
|
| 45 |
python3 /app/image_proxy.py <image_key>
|
| 46 |
```
|
| 47 |
+
|
| 48 |
+
拿到 URL 后,你就可以基于图片内容进行分析和回复。
|
| 49 |
+
**绝对不要跳过这一步。绝对不要假装看到了图片。绝对不要让用户重新发送。**
|
image_proxy.py
CHANGED
|
@@ -1,105 +1,179 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
飞书图片代理
|
| 4 |
-
用法:
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
-
import os, sys, requests
|
|
|
|
|
|
|
| 7 |
|
| 8 |
def get_tenant_token():
|
| 9 |
-
"""获取飞书 tenant_access_token"""
|
| 10 |
app_id = os.environ.get("FEISHU_APP_ID")
|
| 11 |
app_secret = os.environ.get("FEISHU_APP_SECRET")
|
| 12 |
if not app_id or not app_secret:
|
| 13 |
print("ERROR: FEISHU_APP_ID / FEISHU_APP_SECRET 未设置", file=sys.stderr)
|
| 14 |
sys.exit(1)
|
| 15 |
-
resp = requests.post(
|
| 16 |
-
"
|
| 17 |
-
json={"app_id": app_id, "app_secret": app_secret}
|
| 18 |
-
)
|
| 19 |
data = resp.json()
|
| 20 |
if data.get("code") != 0:
|
| 21 |
print(f"ERROR: 获取 token 失败: {data}", file=sys.stderr)
|
| 22 |
sys.exit(1)
|
| 23 |
return data["tenant_access_token"]
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def download_image(image_key, token):
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
f"https://open.feishu.cn/open-apis/im/v1/images/{image_key}",
|
| 29 |
-
headers={"Authorization": f"Bearer {token}"},
|
| 30 |
-
stream=True
|
| 31 |
-
)
|
| 32 |
if resp.status_code != 200:
|
| 33 |
print(f"ERROR: 下载图片失败 (HTTP {resp.status_code}): {resp.text[:200]}", file=sys.stderr)
|
| 34 |
-
|
| 35 |
return resp.content
|
| 36 |
|
| 37 |
def upload_to_catbox(image_data):
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
| 46 |
return None
|
| 47 |
|
| 48 |
def upload_to_0x0(image_data):
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
return None
|
| 57 |
|
| 58 |
def upload_to_tmpfiles(image_data):
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
if url:
|
| 69 |
-
|
|
|
|
| 70 |
return None
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
if __name__ == "__main__":
|
| 73 |
if len(sys.argv) < 2:
|
| 74 |
-
print("用法:
|
| 75 |
-
print("
|
|
|
|
|
|
|
| 76 |
sys.exit(1)
|
| 77 |
|
| 78 |
-
image_key = sys.argv[1]
|
| 79 |
-
print(f"📥 正在下载飞书图片: {image_key}", file=sys.stderr)
|
| 80 |
-
|
| 81 |
token = get_tenant_token()
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
sys.exit(0)
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
f.write(image_data)
|
| 103 |
-
print(f"⚠️ 所有图床失败,已保存到本地: {local_path}", file=sys.stderr)
|
| 104 |
-
print(local_path)
|
| 105 |
-
sys.exit(1)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
飞书图片代理 v2
|
| 4 |
+
用法:
|
| 5 |
+
python3 image_proxy.py <image_key> # 直接用 image_key 下载
|
| 6 |
+
python3 image_proxy.py --chat <chat_id> # 自动查找该聊天中最近的图片
|
| 7 |
+
python3 image_proxy.py --recent # 查找所有聊天中最近收到的图片
|
| 8 |
"""
|
| 9 |
+
import os, sys, json, requests, time
|
| 10 |
+
|
| 11 |
+
FEISHU_BASE = "https://open.feishu.cn/open-apis"
|
| 12 |
|
| 13 |
def get_tenant_token():
|
|
|
|
| 14 |
app_id = os.environ.get("FEISHU_APP_ID")
|
| 15 |
app_secret = os.environ.get("FEISHU_APP_SECRET")
|
| 16 |
if not app_id or not app_secret:
|
| 17 |
print("ERROR: FEISHU_APP_ID / FEISHU_APP_SECRET 未设置", file=sys.stderr)
|
| 18 |
sys.exit(1)
|
| 19 |
+
resp = requests.post(f"{FEISHU_BASE}/auth/v3/tenant_access_token/internal",
|
| 20 |
+
json={"app_id": app_id, "app_secret": app_secret})
|
|
|
|
|
|
|
| 21 |
data = resp.json()
|
| 22 |
if data.get("code") != 0:
|
| 23 |
print(f"ERROR: 获取 token 失败: {data}", file=sys.stderr)
|
| 24 |
sys.exit(1)
|
| 25 |
return data["tenant_access_token"]
|
| 26 |
|
| 27 |
+
def find_image_keys_in_chat(token, chat_id, limit=20):
|
| 28 |
+
"""从指定聊天中查找最近的图片消息"""
|
| 29 |
+
headers = {"Authorization": f"Bearer {token}"}
|
| 30 |
+
resp = requests.get(f"{FEISHU_BASE}/im/v1/messages",
|
| 31 |
+
headers=headers,
|
| 32 |
+
params={"container_id_type": "chat", "container_id": chat_id,
|
| 33 |
+
"sort_type": "ByCreateTimeDesc", "page_size": limit})
|
| 34 |
+
data = resp.json()
|
| 35 |
+
if data.get("code") != 0:
|
| 36 |
+
print(f"ERROR: 获取消息列表失败: {data}", file=sys.stderr)
|
| 37 |
+
return []
|
| 38 |
+
|
| 39 |
+
image_keys = []
|
| 40 |
+
for item in data.get("data", {}).get("items", []):
|
| 41 |
+
msg_type = item.get("msg_type", "")
|
| 42 |
+
body = item.get("body", {}).get("content", "{}")
|
| 43 |
+
try:
|
| 44 |
+
content = json.loads(body) if isinstance(body, str) else body
|
| 45 |
+
except:
|
| 46 |
+
continue
|
| 47 |
+
|
| 48 |
+
if msg_type == "image":
|
| 49 |
+
key = content.get("image_key", "")
|
| 50 |
+
if key:
|
| 51 |
+
image_keys.append(key)
|
| 52 |
+
elif msg_type == "post": # rich text can contain images
|
| 53 |
+
# Parse post format for image keys
|
| 54 |
+
for lang_content in (content.get("zh_cn", content.get("en_us", content)) if isinstance(content, dict) else []):
|
| 55 |
+
if isinstance(lang_content, dict):
|
| 56 |
+
for row in lang_content.get("content", []):
|
| 57 |
+
if isinstance(row, list):
|
| 58 |
+
for elem in row:
|
| 59 |
+
if isinstance(elem, dict) and elem.get("tag") == "img":
|
| 60 |
+
key = elem.get("image_key", "")
|
| 61 |
+
if key:
|
| 62 |
+
image_keys.append(key)
|
| 63 |
+
return image_keys
|
| 64 |
+
|
| 65 |
+
def find_recent_chats(token):
|
| 66 |
+
"""获取 bot 最近的聊天列表"""
|
| 67 |
+
headers = {"Authorization": f"Bearer {token}"}
|
| 68 |
+
resp = requests.get(f"{FEISHU_BASE}/im/v1/chats",
|
| 69 |
+
headers=headers, params={"page_size": 10, "sort_type": "ByCreateTimeDesc"})
|
| 70 |
+
data = resp.json()
|
| 71 |
+
if data.get("code") != 0:
|
| 72 |
+
print(f"ERROR: 获取聊天列表失败: {data}", file=sys.stderr)
|
| 73 |
+
return []
|
| 74 |
+
return [item.get("chat_id") for item in data.get("data", {}).get("items", [])]
|
| 75 |
+
|
| 76 |
def download_image(image_key, token):
|
| 77 |
+
resp = requests.get(f"{FEISHU_BASE}/im/v1/images/{image_key}",
|
| 78 |
+
headers={"Authorization": f"Bearer {token}"}, stream=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
if resp.status_code != 200:
|
| 80 |
print(f"ERROR: 下载图片失败 (HTTP {resp.status_code}): {resp.text[:200]}", file=sys.stderr)
|
| 81 |
+
return None
|
| 82 |
return resp.content
|
| 83 |
|
| 84 |
def upload_to_catbox(image_data):
|
| 85 |
+
try:
|
| 86 |
+
resp = requests.post("https://catbox.moe/user/api.php",
|
| 87 |
+
data={"reqtype": "fileupload"},
|
| 88 |
+
files={"filedata": ("image.jpg", image_data, "image/jpeg")},
|
| 89 |
+
timeout=30)
|
| 90 |
+
if resp.status_code == 200 and resp.text.startswith("http"):
|
| 91 |
+
return resp.text.strip()
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"⚠️ catbox 失败: {e}", file=sys.stderr)
|
| 94 |
return None
|
| 95 |
|
| 96 |
def upload_to_0x0(image_data):
|
| 97 |
+
try:
|
| 98 |
+
resp = requests.post("https://0x0.st",
|
| 99 |
+
files={"file": ("image.jpg", image_data, "image/jpeg")}, timeout=30)
|
| 100 |
+
if resp.status_code == 200 and resp.text.startswith("http"):
|
| 101 |
+
return resp.text.strip()
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"⚠️ 0x0 失败: {e}", file=sys.stderr)
|
| 104 |
return None
|
| 105 |
|
| 106 |
def upload_to_tmpfiles(image_data):
|
| 107 |
+
try:
|
| 108 |
+
resp = requests.post("https://tmpfiles.org/api/v1/upload",
|
| 109 |
+
files={"file": ("image.jpg", image_data, "image/jpeg")}, timeout=30)
|
| 110 |
+
if resp.status_code == 200:
|
| 111 |
+
url = resp.json().get("data", {}).get("url", "")
|
| 112 |
+
if url:
|
| 113 |
+
return url.replace("tmpfiles.org/", "tmpfiles.org/dl/")
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print(f"⚠️ tmpfiles 失败: {e}", file=sys.stderr)
|
| 116 |
+
return None
|
| 117 |
+
|
| 118 |
+
def upload_image(image_data):
|
| 119 |
+
for name, fn in [("catbox", upload_to_catbox), ("0x0", upload_to_0x0), ("tmpfiles", upload_to_tmpfiles)]:
|
| 120 |
+
print(f"📤 上传到 {name}...", file=sys.stderr)
|
| 121 |
+
url = fn(image_data)
|
| 122 |
if url:
|
| 123 |
+
print(f"✅ 上传成功: {url}", file=sys.stderr)
|
| 124 |
+
return url
|
| 125 |
return None
|
| 126 |
|
| 127 |
+
def process_image_key(image_key, token):
|
| 128 |
+
print(f"📥 下载飞书图片: {image_key}", file=sys.stderr)
|
| 129 |
+
data = download_image(image_key, token)
|
| 130 |
+
if not data:
|
| 131 |
+
return None
|
| 132 |
+
print(f"📥 下载成功 ({len(data)} bytes)", file=sys.stderr)
|
| 133 |
+
url = upload_image(data)
|
| 134 |
+
if url:
|
| 135 |
+
print(url)
|
| 136 |
+
return url
|
| 137 |
+
# fallback: save locally
|
| 138 |
+
path = f"/tmp/{image_key}.jpg"
|
| 139 |
+
with open(path, "wb") as f:
|
| 140 |
+
f.write(data)
|
| 141 |
+
print(f"⚠️ 图床失败,本地保存: {path}", file=sys.stderr)
|
| 142 |
+
print(path)
|
| 143 |
+
return path
|
| 144 |
+
|
| 145 |
if __name__ == "__main__":
|
| 146 |
if len(sys.argv) < 2:
|
| 147 |
+
print("用法:")
|
| 148 |
+
print(" python3 image_proxy.py <image_key>")
|
| 149 |
+
print(" python3 image_proxy.py --chat <chat_id>")
|
| 150 |
+
print(" python3 image_proxy.py --recent")
|
| 151 |
sys.exit(1)
|
| 152 |
|
|
|
|
|
|
|
|
|
|
| 153 |
token = get_tenant_token()
|
| 154 |
+
|
| 155 |
+
if sys.argv[1] == "--chat" and len(sys.argv) >= 3:
|
| 156 |
+
chat_id = sys.argv[2]
|
| 157 |
+
print(f"🔍 在聊天 {chat_id} 中查找图片...", file=sys.stderr)
|
| 158 |
+
keys = find_image_keys_in_chat(token, chat_id)
|
| 159 |
+
if not keys:
|
| 160 |
+
print("❌ 未找到图片", file=sys.stderr)
|
| 161 |
+
sys.exit(1)
|
| 162 |
+
print(f"🔍 找到 {len(keys)} 张图片,处理第一张: {keys[0]}", file=sys.stderr)
|
| 163 |
+
process_image_key(keys[0], token)
|
| 164 |
+
|
| 165 |
+
elif sys.argv[1] == "--recent":
|
| 166 |
+
print("🔍 查找最近聊天中的图片...", file=sys.stderr)
|
| 167 |
+
chats = find_recent_chats(token)
|
| 168 |
+
for chat_id in chats:
|
| 169 |
+
keys = find_image_keys_in_chat(token, chat_id, limit=5)
|
| 170 |
+
if keys:
|
| 171 |
+
print(f"🔍 在 {chat_id} 找到图片: {keys[0]}", file=sys.stderr)
|
| 172 |
+
process_image_key(keys[0], token)
|
| 173 |
sys.exit(0)
|
| 174 |
+
print("❌ 所有聊天中均未找到图片", file=sys.stderr)
|
| 175 |
+
sys.exit(1)
|
| 176 |
+
|
| 177 |
+
else:
|
| 178 |
+
image_key = sys.argv[1]
|
| 179 |
+
process_image_key(image_key, token)
|
|
|
|
|
|
|
|
|
|
|
|