asemxin commited on
Commit
32aeb2a
·
1 Parent(s): 191d20d

feat: image_proxy v2 + stronger SOUL.md

Browse files
Files changed (2) hide show
  1. SOUL.md +16 -3
  2. image_proxy.py +142 -68
SOUL.md CHANGED
@@ -28,9 +28,22 @@
28
  - 对数字化工具(SAP、Oracle、WMS、TMS、APS)信手拈来
29
  - 敢于挑战传统思维,提出反直觉的洞察
30
 
31
- ## 图片处理
32
- 当用户发送图片时,消息中会包含 image_key(格式如 `img_v3_xxx`)。你必须立即执行以下命令下载并获取图片 URL
 
 
 
 
 
 
 
 
 
 
 
33
  ```bash
34
  python3 /app/image_proxy.py <image_key>
35
  ```
36
- 拿到 URL 后用 `web_fetch` 获取图片内容进行分析。不要跳过这一步,不要让用户重新发送图片。
 
 
 
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
- 飞书图片代理:下载飞书消息中的图片 → 上传到免费图床 → 返回可访问 URL
4
- 用法: python3 image_proxy.py <image_key>
 
 
 
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
- "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
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
- """从飞书 API 下载图片"""
27
- resp = requests.get(
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
- sys.exit(1)
35
  return resp.content
36
 
37
  def upload_to_catbox(image_data):
38
- """上传到 catbox.moe(免费,无需 API Key)"""
39
- resp = requests.post(
40
- "https://catbox.moe/user/api.php",
41
- data={"reqtype": "fileupload"},
42
- files={"filedata": ("image.jpg", image_data, "image/jpeg")}
43
- )
44
- if resp.status_code == 200 and resp.text.startswith("http"):
45
- return resp.text.strip()
 
46
  return None
47
 
48
  def upload_to_0x0(image_data):
49
- """备用:上传到 0x0.st"""
50
- resp = requests.post(
51
- "https://0x0.st",
52
- files={"file": ("image.jpg", image_data, "image/jpeg")}
53
- )
54
- if resp.status_code == 200 and resp.text.startswith("http"):
55
- return resp.text.strip()
56
  return None
57
 
58
  def upload_to_tmpfiles(image_data):
59
- """备用:上传到 tmpfiles.org"""
60
- resp = requests.post(
61
- "https://tmpfiles.org/api/v1/upload",
62
- files={"file": ("image.jpg", image_data, "image/jpeg")}
63
- )
64
- if resp.status_code == 200:
65
- data = resp.json()
66
- url = data.get("data", {}).get("url", "")
67
- # tmpfiles.org 返回的 URL 需要加 /dl/ 才能直接访问
 
 
 
 
 
 
68
  if url:
69
- return url.replace("tmpfiles.org/", "tmpfiles.org/dl/")
 
70
  return None
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  if __name__ == "__main__":
73
  if len(sys.argv) < 2:
74
- print("用法: python3 image_proxy.py <image_key>")
75
- print(" 从飞书下载图片并上传到免费图床,返回可访问的 URL")
 
 
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
- image_data = download_image(image_key, token)
83
- print(f"📥 下载成功 ({len(image_data)} bytes)", file=sys.stderr)
84
-
85
- # 依次尝试多个
86
- for name, uploader in [("catbox.moe", upload_to_catbox),
87
- ("0x0.st", upload_to_0x0),
88
- ("tmpfiles.org", upload_to_tmpfiles)]:
89
- print(f"📤 正在上传到 {name}...", file=sys.stderr)
90
- try:
91
- url = uploader(image_data)
92
- if url:
93
- print(f"✅ 上传成功: {url}", file=sys.stderr)
94
- print(url) # stdout 只输出 URL,供 agent 捕获
 
 
 
 
 
 
95
  sys.exit(0)
96
- except Exception as e:
97
- print(f"⚠️ {name} 上传失败: {e}", file=sys.stderr)
98
-
99
- # 所有图床都失败,保存到本地
100
- local_path = f"/tmp/{image_key}.jpg"
101
- with open(local_path, "wb") as f:
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)