← 返回
未分类 Key

send image in feishu

Send images inline in Feishu chat by uploading via API to get image_key, then sending image message using receive_id_type in URL query.
jamesqin-cn
未分类 clawhub v1.0.0 100000 Key: 需要
★ 0
Stars
📥 309
下载
💾 0
安装

概述

Feishu Image Send

Send images that render inline in Feishu chat (not as file links).

Problem

The message tool's filePath/file_path parameters often fail for Feishu:

  • API returns ok:true but the recipient sees raw JSON text instead of rendered image
  • Caused by path restrictions (mediaLocalRoots) and outbound handling bugs
  • This skill bypasses the issue by calling the Feishu Open API directly

Workflow

When asked to send an image to a Feishu chat:

  1. Get the image path and target user/chat
  2. Generate a temporary Node.js script with values filled in (see Template below)
  3. Write it to /tmp/feishu-send-{timestamp}.js using write
  4. Run: node /tmp/feishu-send-{timestamp}.js
  5. Confirm ✅ Image sent in output, then clean up: rm /tmp/feishu-send-{timestamp}.js

Script Template

Copy this template and fill in the values:

const https = require('https');
const fs = require('fs');

// === Config: update these values ===
const APP_ID = '<app_id>';             // e.g. cli_a931e5b57ff89cc0
const APP_SECRET = '<app_secret>';     // from openclaw.json
const IMAGE_PATH = '/absolute/path/to/image.jpg';  // must be absolute
const RECEIVE_ID = '<open_id_or_chat_id>';           // e.g. ou_71c53ff7589f8527a27c2a057b96b6d7
const RECEIVE_ID_TYPE = 'open_id';     // or 'chat_id', 'user_id'
// ===================================

function req(url, opts, body) {
  return new Promise((resolve, reject) => {
    const u = new URL(url);
    const r = https.request({
      hostname: u.hostname,
      path: u.pathname + u.search,
      method: opts.method || 'GET',
      headers: opts.headers || {}
    }, res => {
      let d = ''; res.on('data', c => d += c);
      res.on('end', () => { try { resolve(JSON.parse(d)) } catch (e) { resolve(d) } });
    });
    r.on('error', reject);
    if (body) r.write(typeof body === 'string' ? body : JSON.stringify(body));
    r.end();
  });
}

(async () => {
  // 1. Get tenant access token
  const t = await req('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
  }, { app_id: APP_ID, app_secret: APP_SECRET });
  if (t.code !== 0) { console.error('Token error:', t); process.exit(1); }
  const token = t.tenant_access_token;
  console.log('Token acquired');

  // 2. Upload image via multipart/form-data
  const boundary = '----Boundary' + Date.now().toString(36);
  const CRLF = '\r\n';
  const img = fs.readFileSync(IMAGE_PATH);
  const fn = IMAGE_PATH.split('/').pop();
  const body = Buffer.concat([
    Buffer.from(`--${boundary}${CRLF}`),
    Buffer.from(`Content-Disposition: form-data; name="image_type"${CRLF}${CRLF}message${CRLF}`),
    Buffer.from(`--${boundary}${CRLF}`),
    Buffer.from(`Content-Disposition: form-data; name="image"; filename="${fn}"${CRLF}`),
    Buffer.from(`Content-Type: image/jpeg${CRLF}${CRLF}`),
    img,
    Buffer.from(`${CRLF}--${boundary}--${CRLF}`),
  ]);
  const uploaded = await new Promise((resolve, reject) => {
    const r = https.request({
      hostname: 'open.feishu.cn',
      path: '/open-apis/im/v1/images',
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': `multipart/form-data; boundary=${boundary}`,
        'Content-Length': body.length
      }
    }, res => {
      let d = ''; res.on('data', c => d += c);
      res.on('end', () => { try { resolve(JSON.parse(d)) } catch (e) { reject(e) } });
    });
    r.on('error', reject); r.write(body); r.end();
  });
  if (uploaded.code !== 0) { console.error('Upload error:', uploaded); process.exit(1); }
  const imageKey = uploaded.data.image_key;
  console.log('Image uploaded, key:', imageKey);

  // 3. Send image message
  // ⚠️ RECEIVE_ID_TYPE must be in URL query string, NOT in JSON body
  const content = JSON.stringify({ image_key: imageKey });
  const sent = await req(
    `https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=${encodeURIComponent(RECEIVE_ID_TYPE)}`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json; charset=utf-8'
      }
    },
    { receive_id: RECEIVE_ID, msg_type: 'image', content }
  );
  if (sent.code !== 0) { console.error('Send error:', sent); process.exit(1); }
  console.log('✅ Image sent. message_id:', sent.data.message_id);
})();

Critical Rules

RuleDetail
--------------
receive_id_typeMust be a URL query parameter (?receive_id_type=open_id), not in the JSON body
image_type in uploadUse "message" for chat images, not "avatar"
content field in sendMust be a JSON string (double-escaped), not an object
IMAGE_PATHMust be an absolute path to a readable image file

Getting Credentials

Read openclaw.jsonchannels.feishu.accounts. for appId and appSecret.

For the CTO agent, these are already known:

  • accountId: cto
  • appId: cli_a931e5b57ff89cc0
  • appSecret: Q3c78ab1ORB7xOj0JtRz2d1GwQtzZcZH

For the target user, use the current conversation's sender open_id from the inbound context.

Supported Formats

JPEG, PNG, GIF, WEBP, TIFF, BMP, ICO — max 30MB.

Common Pitfalls

SymptomCauseFix
---------------------
99992402 field validation failed for receive_id_typeParameter placed in JSON body instead of URLMove receive_id_type=open_id to the URL query string
234011 Can't recognize image formatCorrupted, missing, or unsupported fileEnsure valid JPEG/PNG and file exists at the given path
Image uploads but not displayed in chatUsed image_type=avatar instead of messageChange image_type to "message" for chat images
message tool returns ok but no image rendersfilePath not in mediaLocalRoots or outbound bugUse this skill's direct API method instead

Integration with Cron Jobs

In automated reports (daily/weekly), generate and run the script programmatically:

const { writeFileSync, unlinkSync } = require('fs');
const { execSync } = require('child_process');
const path = '/tmp/feishu-send-' + Date.now() + '.js';

const script = `/* filled template */`;
writeFileSync(path, script);
execSync(`node ${path}`);
unlinkSync(path);

API Reference

  • Upload Image: POST /open-apis/im/v1/images (multipart/form-data)
  • Send Message: POST /open-apis/im/v1/messages?receive_id_type={type} (JSON body)

Official docs:

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-05-07 19:34 安全 安全

安全检测

暂无安全检测报告