agentsclimarketplace

Discord bot image to claude

Skill bokuwalily/claude-code-skills/skills/discord-bot-image-to-claude

自作Discord gateway bot(claude -p 連携)で、Discordに貼った画像をClaudeに認識させたい時。添付をローカルDL→パスをプロンプトに添えてReadさせる。From its SKILL.md

Install
npx -y skills add bokuwalily/claude-code-skills --skill discord-bot-image-to-claude

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

3.6 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

Procedure

~/.discord/bridge.mjs のような自作 Discord⇄claude bot は、既定で m.content(テキスト) しか claude に渡さず、画像添付(m.attachments)を無視する。画像を認識させるには 添付をローカルに落とし、その絶対パスをプロンプトに添えて claude の Read ツールに読ませる。 (claude CLI はローカル画像ファイルを Read で読める。allowed_toolsRead が要る)

1. 画像DLヘルパー(依存ゼロ・Node24 fetch)

const ATT_DIR = `${DDIR}/attachments`;
const isImageAtt = (a) =>
  (a.content_type || "").startsWith("image/") ||
  /\.(png|jpe?g|gif|webp)$/i.test(a.filename || "");

async function downloadImages(atts, msgId) {
  const imgs = (atts || []).filter(isImageAtt).slice(0, 8); // 最大8枚
  if (!imgs.length) return [];
  try { mkdirSync(ATT_DIR, { recursive: true }); } catch {}
  const paths = [];
  for (let i = 0; i < imgs.length; i++) {
    const a = imgs[i];
    try {
      const res = await fetch(a.url);                       // Discord CDNは認証不要
      if (!res.ok) continue;
      const buf = Buffer.from(await res.arrayBuffer());
      const safe = (a.filename || `img${i}.png`).replace(/[^\w.\-]/g, "_");
      const p = `${ATT_DIR}/${msgId}_${i}_${safe}`;
      writeFileSync(p, buf); paths.push(p);
    } catch (e) { log("img dl fail", a.url, e?.message || e); }
  }
  return paths;
}

2. ハンドラ: テキスト無し画像のみも受理

const text = (m.content || "").trim();
if (text.startsWith("//") || text.startsWith("#")) return;     // メモはスルー(画像付きでも)
const imgAtts = (m.attachments || []).filter(isImageAtt);
if (!text && imgAtts.length === 0) return;                     // 両方無ければ無視

3. プロンプト合成 → 実行 → finallyで後始末

let imgPaths = [];
try {
  imgPaths = await downloadImages(imgAtts, m.id);
  let prompt = text;
  if (imgPaths.length) {
    const list = imgPaths.map((p) => `- ${p}`).join("\n");
    prompt = `${text}\n\n[添付画像 ${imgPaths.length}枚。Readツールで読み、内容を踏まえて応答せよ]\n${list}`.trim();
  }
  const res = await runClaude({ prompt, /* ...既存 */ });
  // ...
} finally {
  for (const p of imgPaths) { try { unlinkSync(p); } catch {} } // 一時画像を削除
}

import に mkdirSync, unlinkSync を追加。反映は launchctl kickstart -k gui/$(id -u)/<label>

Pitfalls

  • allowed_tools(と壁打ちモードのRO_TOOLS)に Read が無いと claude が画像を開けない。両方確認。
  • Discord添付URL(a.url)は署名付きで一時的。受信直後にDLする(後で取りに行くと失効)。
  • 一時ファイルは finally で必ず削除。残すと ~/.discord/attachments/ が肥大する。
  • 枚数上限(8)とファイル名サニタイズ([^\w.\-]→_)は入れる。巨大/悪意ファイル名対策。
  • このbotがRESUME再配信で二重処理する問題は別件 → を併用。

Verification

  • node --check bridge.mjs で構文OK、再起動後ログに READY as <bot>
  • 画像1枚を該当chに貼る→ ▶️ 受付: 🖼 画像1枚(テキスト無し時)が出て、claudeが内容に言及した応答を返す。
  • 処理後 ls ~/.discord/attachments/ が空(後始末されている)。

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.