实战完整示例

奶狗 发表于 4 周前 浏览 40 字数 2883 阅读时长 15分钟

6. 实战完整示例

6.1 示例 A:命令型插件(含配置 + 图片)

参考 plugins/aidraw/index.js,通过命令在 QQ/微信/网页端生成 AI 图片:

/**
 * AI 绘图插件
 * 命令入口:/draw /绘图 /imagine /生图
 */
const path = require('path');
const fs = require('fs');
const os = require('os');
const axios = require('axios');
const db = require('../../lib/db');
const settings = require('../../lib/settings');

const PLUGIN_ID = 'aidraw';
const PREFIXES = ['/draw', '/绘图', '/imagine', '/生图'];

async function getSetting(botId, key, fallback) {
  const row = await db.row(
    'SELECT config_value FROM plugin_settings WHERE bot_id=? AND plugin_id=? AND config_key=?',
    [botId, PLUGIN_ID, key]
  );
  if (row && row.config_value != null && String(row.config_value).trim() !== '') return row.config_value;
  return fallback;
}

async function loadConfig(botId) {
  const apiBase = (await getSetting(botId, 'draw_api_base', await settings.getSetting('ai_image_gen_api_base', ''))).trim();
  const apiKey  = (await getSetting(botId, 'draw_api_key',  await settings.getSetting('ai_image_gen_api_key', ''))).trim();
  const model   = (await getSetting(botId, 'draw_model',   await settings.getSetting('ai_image_gen_model', ''))).trim();
  const size    = (await getSetting(botId, 'draw_size',    await settings.getSetting('ai_image_gen_size', '1024x1024'))).trim();
  return { apiBase, apiKey, model, size };
}

module.exports = {
  meta: {
    id: PLUGIN_ID,
    name: 'AI 绘图',
    version: '1.0.0',
    author: '奶狗',
    category: 'AI 创作',
    description: '通过命令在 QQ/微信/网页端生成 AI 图片,支持自定义 OpenAI 兼容生图接口。',
    entry: 'aidraw/index.js',
    commandPrefix: PREFIXES,
    configurable: true,
  },

  async onMessage(msg, ctx) {
    const text = (msg.content || '').trim();
    let prompt = null;
    for (const p of PREFIXES) {
      if (text === p) { prompt = ''; break; }
      if (text.startsWith(p)) { prompt = text.slice(p.length).replace(/^[\s::]+/, '').trim(); break; }
    }
    if (prompt === null) return;            // 非本插件命令,放行

    const botId = (ctx.bot && ctx.bot.id) || 0;
    if (/^(help|帮助|h)$/i.test(prompt)) { await ctx.sendText('发送 /draw 一只猫'); return true; }
    if (!prompt) { await ctx.sendText('请输入提示词,如 /draw 一只猫'); return true; }

    const cfg = await loadConfig(botId);
    await ctx.sendText('🎨 正在生成:' + prompt);
    try {
      const url = cfg.apiBase.replace(/\/+$/, '') + '/v1/images/generations';
      const resp = await axios.post(url, {
        prompt, n: 1, size: cfg.size || '1024x1024', response_format: 'b64_json',
        ...(cfg.model ? { model: cfg.model } : {}),
      }, { headers: { Authorization: 'Bearer ' + cfg.apiKey }, timeout: 120000 });

      const img = (resp.data.data || [])[0];
      const buf = img.b64_json ? Buffer.from(img.b64_json, 'base64')
                               : Buffer.from((await axios.get(img.url, { responseType: 'arraybuffer' })).data);
      const fpath = path.join(os.tmpdir(), 'aidraw_' + Date.now() + '.png');
      fs.writeFileSync(fpath, buf);
      try { await ctx.sendMedia(fpath, 'image'); }
      finally { try { fs.unlinkSync(fpath); } catch (_) {} }
    } catch (e) {
      await ctx.sendText('生成失败:' + e.message);
    }
    return true;
  },
};

6.2 示例 B:被动钩子插件(监听群消息)

const hooks = require('../../lib/hooks');

// 模块加载即订阅
hooks.on('group_message', async ({ msg, ctx, bot }) => {
  if (/谢谢/.test(msg.content || '')) {
    await ctx.sendText('不客气~');
  }
});

module.exports = {
  meta: {
    id: 'thanks-bot',
    name: '自动答谢',
    version: '1.0.0',
    author: '开发者',
    category: '消息处理',
    description: '群聊中有人发"谢谢"时自动回应。',
    usage: '群聊自动生效,无需命令',
  },
  async onMessage() { return false; },  // 不主动处理,仅用钩子
};