Skip to content

完整示例

三个由浅入深的完整插件实现,全部可直接编译运行。它们综合运用了事件响应、消息构造、存储与通知事件。

示例一:关键词回复插件

被动监听群消息(不需要 @),命中关键词就回复。

go
package pluginkeyword

import (
	"context"
	"strings"

	"github.com/jeanhua/AniaBot/common/bot"
	"github.com/jeanhua/AniaBot/common/model/command"
	"github.com/jeanhua/AniaBot/common/model/message"
	"github.com/jeanhua/AniaBot/common/msgchain"
	"github.com/jeanhua/AniaBot/common/plugin"
	"github.com/jeanhua/AniaBot/common/plugininfo"
)

type KeywordPlugin struct {
	plugin.Meta
}

func NewPlugin() *KeywordPlugin {
	return &KeywordPlugin{
		Meta: plugin.Meta{
			Name:      "关键词回复插件",
			HelpWords: "说「早上好」我会回应你哦",
			Order:     plugin.LevelNormal,
			ShowFor:   plugininfo.ShowForGroup,
		},
	}
}

func (p *KeywordPlugin) OnGroupMsg(ctx context.Context, b bot.Bot, cmd command.Command, msg message.Message) (bool, error) {
	var reply string
	switch {
	case strings.Contains(msg.RawMessage, "早上好"):
		reply = "早上好呀 ☀️"
	case strings.Contains(msg.RawMessage, "晚安"):
		reply = "晚安,做个好梦 🌙"
	default:
		return true, nil // 未命中,放行
	}

	chain := msgchain.Builder().Group()
	chain.Mention(msg.Sender.UserId)
	chain.Text(" " + reply)
	b.SendGroupMsg(msg.GroupId, chain.Build())

	// 返回 true 继续放行:聊天类回复不该阻断后续插件(如 AI 对话)
	return true, nil
}

要点msg.RawMessage 是消息纯文本;轻量回应后返回 true 让消息继续传播。

示例二:签到插件

综合运用命令解析、持久化存储与每日 TTL 缓存。

go
package pluginsignin

import (
	"context"
	"fmt"
	"time"

	"github.com/jeanhua/AniaBot/common/bot"
	"github.com/jeanhua/AniaBot/common/model/command"
	"github.com/jeanhua/AniaBot/common/model/message"
	"github.com/jeanhua/AniaBot/common/msgchain"
	"github.com/jeanhua/AniaBot/common/plugin"
	"github.com/jeanhua/AniaBot/common/plugininfo"
	"github.com/jeanhua/AniaBot/common/storage"
)

type SignInPlugin struct {
	plugin.Meta
}

func NewPlugin() *SignInPlugin {
	return &SignInPlugin{
		Meta: plugin.Meta{
			Name:      "签到插件",
			HelpWords: "@我发送 /sign 每日签到,/sign rank 查看我的积分",
			Order:     plugin.LevelNormal,
			ShowFor:   plugininfo.ShowForGroup,
		},
	}
}

func (p *SignInPlugin) OnGroupMsg(ctx context.Context, b bot.Bot, cmd command.Command, msg message.Message) (bool, error) {
	if !cmd.Mention || cmd.Name != "sign" {
		return true, nil
	}

	uid := msg.Sender.UserId.String()
	today := time.Now().Format("2006-01-02")

	if len(cmd.Args) > 0 && cmd.Args[0] == "rank" {
		p.replyRank(ctx, b, msg, uid)
		return false, nil
	}

	// 缓存层:当天签到标记,TTL 到期自然清除
	signKey := "signed:" + today
	if _, signed := p.Storage.GetString(ctx, signKey+":"+uid); signed {
		p.reply(ctx, b, msg, "今天已经签过到了,明天再来吧~")
		return false, nil
	}

	// 持久层:累计积分(按用户隔离命名空间)
	scores := p.PersistentStorage.Clone("score")
	var score int
	scores.Get(ctx, uid, &score)
	score += 10
	scores.Set(ctx, uid, score)

	p.Storage.SetString(ctx, signKey+":"+uid, "1", storage.WithTTL(48*time.Hour))
	p.reply(ctx, b, msg, fmt.Sprintf("签到成功!积分 +10,当前累计 %d 分 🎉", score))
	return false, nil
}

func (p *SignInPlugin) replyRank(ctx context.Context, b bot.Bot, msg message.Message, uid string) {
	scores := p.PersistentStorage.Clone("score")
	var score int
	scores.Get(ctx, uid, &score)
	p.reply(ctx, b, msg, fmt.Sprintf("你当前的积分是 %d 分", score))
}

func (p *SignInPlugin) reply(ctx context.Context, b bot.Bot, msg message.Message, text string) {
	chain := msgchain.Builder().Group()
	chain.Mention(msg.Sender.UserId)
	chain.Text(" " + text)
	if _, ok := b.SendGroupMsg(msg.GroupId, chain.Build()); !ok {
		p.Logger.Error("签到回复发送失败", "group", msg.GroupId)
	}
}

要点

  • 缓存层(Redis/内存)负责「今天是否已签到」—— TTL 到期自动清除
  • 持久层(SQLite/MySQL)负责积分 —— 重启不丢
  • Clone("score") 在插件命名空间内再做子隔离

示例三:新人欢迎插件

使用通知事件而非消息事件 —— 群成员增加时自动欢迎。

go
package pluginwelcome

import (
	"context"

	"github.com/jeanhua/AniaBot/common/bot"
	"github.com/jeanhua/AniaBot/common/model/message"
	"github.com/jeanhua/AniaBot/common/msgchain"
	"github.com/jeanhua/AniaBot/common/plugin"
	"github.com/jeanhua/AniaBot/common/plugininfo"
)

type WelcomePlugin struct {
	plugin.Meta
}

func NewPlugin() *WelcomePlugin {
	return &WelcomePlugin{
		Meta: plugin.Meta{
			Name:      "新人欢迎插件",
			HelpWords: "自动欢迎新入群的成员",
			Order:     plugin.LevelNormal,
			ShowFor:   plugininfo.ShowForGroup,
		},
	}
}

// OnGroupIncrease 群成员增加通知(广播制,所有插件都会收到)
func (p *WelcomePlugin) OnGroupIncrease(ctx context.Context, b bot.Bot, notice message.GroupIncreaseNotice) error {
	chain := msgchain.Builder().Group()
	chain.Mention(notice.UserId)
	chain.Text(" 欢迎新同学加入!🎉 发送 @我 /help 可以查看我会的技能哦")

	if _, ok := b.SendGroupMsg(notice.GroupId, chain.Build()); !ok {
		p.Logger.Error("欢迎消息发送失败", "group", notice.GroupId, "user", notice.UserId)
	}
	return nil
}

// OnGroupDecrease 成员离开时也可以做点什么
func (p *WelcomePlugin) OnGroupDecrease(ctx context.Context, b bot.Bot, notice message.GroupDecreaseNotice) error {
	p.Logger.Info("成员离开",
		"group", notice.GroupId,
		"user", notice.UserId,
		"subType", notice.SubType, // leave / kick / kick_me
	)
	return nil
}

要点

  • 通知事件共 14 种,全部广播给所有插件,返回 error 不影响其他插件
  • notice.SubType 区分入群方式:approve(审批)/ invite(邀请)

更多真实示例

  • 内置插件源码:bot/plugins/ 下的七个插件(系统、日志、复读机、防撤回、请求拦截、AI 对话、每日新闻都是优秀的学习样本)
  • 自定义插件模板:仓库 custom/ 目录下的 plugins/pluginexample(带注释的插件骨架)与 mvp(最小可运行示例)

基于 MIT 许可证发布