一个微信接口,拿去用吧,最大支持10M大小
演示图片 https://chatbotcos.weixin.qq.com/chatbot/unlimited-openaiassets_1794c938ff3ffd23f1fb3f9d36603e0b_5679931784639978482.jpg
开源地址 https://github.com/lixing23/wechat-chatbot-image-api
import hashlib
import hmac
import json
import mimetypes
import os
import time
import uuid
import requests
APP_ID = "V4poszKZgOynfZUpQvoMWtJcprqIOK"
APP_KEY = "openchatai!2o19"
SECRET = "d637ab9fb119a1e652ab3d3dec89f7e744e4d5a0e0f"
PAGE_URL = f"https://chatbot.weixin.qq.com/webapp/{APP_ID}?robotName=17173%E5%B0%8F%E5%A6%96"
TOKEN_URL = f"https://chatbot.weixin.qq.com/weixinh5/webapp/gettoken/{APP_ID}"
UPLOAD_URL = f"https://chatbot.weixin.qq.com/weixinh5/webapp/{APP_ID}/cos/upload?permanent=false"
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif"}
MAX_SIZE = 10 * 1024 * 1024
def normalize_data(value):
if isinstance(value, dict):
return {key: normalize_data(value[key]) for key in sorted(value)}
if isinstance(value, list):
return [normalize_data(item) for item in value]
return value
def make_sign(timestamp, nonce, data=None):
if data is None:
data = {}
data_json = json.dumps(
normalize_data(data),
ensure_ascii=False,
separators=(",", ":")
)
parts = [
f"appKey={APP_KEY}",
f"timestamp={timestamp}",
f"nonce={nonce}",
f"data={data_json}",
]
sign_text = "".join(sorted(parts)).replace("=", "")
return hmac.new(
SECRET.encode("utf-8"),
sign_text.encode("utf-8"),
hashlib.sha256
).hexdigest()
def build_signed_headers(extra_headers=None, data=None):
timestamp = int(time.time())
nonce = uuid.uuid4().hex[:16]
sign = make_sign(timestamp, nonce, data or {})
headers = {
"Accept": "application/json, text/plain, */*",
"Origin": "https://chatbot.weixin.qq.com",
"Referer": PAGE_URL,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
"timestamp": str(timestamp),
"nonce": nonce,
"sign": sign,
}
if extra_headers:
headers.update(extra_headers)
return headers
def get_token():
headers = build_signed_headers()
resp = requests.get(TOKEN_URL, headers=headers, timeout=30)
resp.raise_for_status()
data = resp.json()
token = data.get("token") or data.get("data", {}).get("token")
if not token:
raise RuntimeError(f"获取 token 失败: {data}")
return token
def validate_image(file_path):
ext = os.path.splitext(file_path)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise RuntimeError(f"不支持的图片格式: {ext}")
size = os.path.getsize(file_path)
if size >= MAX_SIZE:
raise RuntimeError("图片必须小于 10MB")
def upload_image(file_path):
validate_image(file_path)
token = get_token()
mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
headers = build_signed_headers({
"x-client-token": token,
"request_id": str(uuid.uuid4()),
})
filename = os.path.basename(file_path)
with open(file_path, "rb") as f:
files = {
"media": (filename, f, mime_type)
}
resp = requests.post(
UPLOAD_URL,
headers=headers,
files=files,
timeout=120
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0 or not data.get("url"):
raise RuntimeError(f"上传失败: {data}")
return data["url"], data
def check_public_image_url(url):
resp = requests.get(
url,
headers={
"User-Agent": "Mozilla/5.0",
"Referer": "https://chatbot.weixin.qq.com/",
},
timeout=30
)
content_type = resp.headers.get("Content-Type", "")
if resp.status_code == 200 and content_type.startswith("image/"):
return True
return False
if __name__ == "__main__":
url, raw = upload_image("test.png")
print("返回 URL:", url)
print("原始响应:", raw)
if check_public_image_url(url):
print("直链可访问")
else:
print("上传成功,但直链不可公开访问")