package help import ( "fmt" "sort" "strings" "git.nakama.town/fmartingr/butterrobot/internal/db" "git.nakama.town/fmartingr/butterrobot/internal/model" "git.nakama.town/fmartingr/butterrobot/internal/plugin" "golang.org/x/exp/slog" ) // ChannelPluginGetter is an interface for getting channel plugins type ChannelPluginGetter interface { GetChannelPlugins(channelID int64) ([]*model.ChannelPlugin, error) GetChannelPluginsFromPlatformID(platform, platformChannelID string) ([]*model.ChannelPlugin, error) } // HelpPlugin provides help information about available commands type HelpPlugin struct { plugin.BasePlugin db ChannelPluginGetter } // New creates a new HelpPlugin instance func New(db ChannelPluginGetter) *HelpPlugin { return &HelpPlugin{ BasePlugin: plugin.BasePlugin{ ID: "utility.help", Name: "Help", Help: "Shows available commands when you type '!help'", }, db: db, } } // OnMessage handles incoming messages func (p *HelpPlugin) OnMessage(msg *model.Message, config map[string]interface{}, cache model.CacheInterface) []*model.MessageAction { // Check if message is the help command if !strings.EqualFold(strings.TrimSpace(msg.Text), "!help") { return nil } // Get channel plugins from database using platform and platform channel ID channelPlugins, err := p.db.GetChannelPluginsFromPlatformID(msg.Channel.Platform, msg.Channel.PlatformChannelID) if err != nil && err != db.ErrNotFound { slog.Error("Failed to get channel plugins", slog.Any("err", err)) return []*model.MessageAction{} } // If no plugins found, initialize empty slice if err == db.ErrNotFound { channelPlugins = []*model.ChannelPlugin{} } // Get all available plugins availablePlugins := plugin.GetAvailablePlugins() // Filter to only enabled plugins for this channel enabledPlugins := make(map[string]model.Plugin) for _, channelPlugin := range channelPlugins { if channelPlugin.Enabled { if availablePlugin, exists := availablePlugins[channelPlugin.PluginID]; exists { enabledPlugins[channelPlugin.PluginID] = availablePlugin } } } // If no plugins are enabled, return a message if len(enabledPlugins) == 0 { response := &model.Message{ Text: "No plugins are currently enabled for this channel.", Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, } return []*model.MessageAction{ { Type: model.ActionSendMessage, Message: response, Chat: msg.Chat, Channel: msg.Channel, }, } } // Group plugins by category categories := map[string][]model.Plugin{ "Development": {}, "Fun and Entertainment": {}, "Utility": {}, "Security": {}, "Social Media": {}, "Other": {}, } // Categorize plugins based on their ID prefix for _, p := range enabledPlugins { category := p.GetID() switch { case strings.HasPrefix(category, "dev."): categories["Development"] = append(categories["Development"], p) case strings.HasPrefix(category, "fun."): categories["Fun and Entertainment"] = append(categories["Fun and Entertainment"], p) case strings.HasPrefix(category, "util.") || strings.HasPrefix(category, "reminder.") || strings.HasPrefix(category, "utility."): categories["Utility"] = append(categories["Utility"], p) case strings.HasPrefix(category, "security."): categories["Security"] = append(categories["Security"], p) case strings.HasPrefix(category, "social."): categories["Social Media"] = append(categories["Social Media"], p) default: categories["Other"] = append(categories["Other"], p) } } // Build the help message var helpText strings.Builder helpText.WriteString("🤖 **Available Commands**\n\n") // Sort category names for consistent output categoryOrder := []string{"Development", "Fun and Entertainment", "Utility", "Security", "Social Media", "Other"} for _, categoryName := range categoryOrder { pluginList := categories[categoryName] if len(pluginList) == 0 { continue } // Sort plugins within category by name sort.Slice(pluginList, func(i, j int) bool { return pluginList[i].GetName() < pluginList[j].GetName() }) helpText.WriteString(fmt.Sprintf("**%s:**\n", categoryName)) for _, p := range pluginList { if p.GetHelp() == "" { continue } helpText.WriteString(fmt.Sprintf("• **%s** - %s\n", p.GetName(), p.GetHelp())) } helpText.WriteString("\n") } // Add footer helpText.WriteString("_Use the specific commands or triggers mentioned above to interact with the bot._") response := &model.Message{ Text: helpText.String(), Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, } return []*model.MessageAction{ { Type: model.ActionSendMessage, Message: response, Chat: msg.Chat, Channel: msg.Channel, }, } }