Команды
Регистрация
func (m *Weather) Commands() map[string]goroku.CommandHandler {
return map[string]goroku.CommandHandler{
"weather": m.WeatherCmd,
}
}Ключ карты — слово команды. С префиксом по умолчанию . это регистрирует .weather. Регистр в ключах не важен.
Имена команд и алиасов уникальны среди всех загруженных модулей. Если weather уже занят другим модулем, регистрация упадёт с ошибкой коллизии, где названо конфликтующее имя, а не молча перезапишет чужую команду.
Алиасы
Добавьте CommandMetas — это опциональный интерфейс, объявить метод достаточно:
func (m *Weather) CommandMetas() map[string]goroku.CommandMeta {
return map[string]goroku.CommandMeta{
"weather": {
Aliases: []string{"w", "wx"},
},
}
}Теперь работают .weather, .w и .wx.
Ограничение места вызова
Поля CommandMeta — это фильтры. Диспетчер проверяет их до запуска обработчика, поэтому неподходящая команда просто не срабатывает.
func (m *Weather) CommandMetas() map[string]goroku.CommandMeta {
return map[string]goroku.CommandMeta{
"weather": {
Aliases: []string{"w"},
OnlyOwner: true, // только вы
OnlyGroups: true, // только в группах
NoForwarded: true, // игнорировать пересланные
},
}
}Часто используемые:
| Поле | Действие |
|---|---|
OnlyOwner | Только владелец аккаунта |
OnlyPM / NoPM | Только / никогда в личных чатах |
OnlyGroups, OnlyChannels, OnlyChats | Ограничение по типу чата |
OnlyReply / NoReply | Требовать / запрещать ответ на сообщение |
OnlyMedia, OnlyPhotos, OnlyVideos, OnlyDocs | Требовать вложение |
NoMedia, NoStickers, NoAudio, NoDoc | Отклонять вложения |
Ratelimit | Применять лимиты частоты по пользователю и чату |
Filter | func(*Message) bool для всего остального |
Полный список — в CommandMeta.
Совпадение по содержимому
"weather": {
StartsWith: "город ",
Regex: `^\d{5}$`,
},Regex компилируется один раз при регистрации. Некорректный шаблон уронит регистрацию, а не будет молча никогда не совпадать.
Ошибки
Верните ошибку — и пользователь её увидит:
func (m *Weather) WeatherCmd(msg *goroku.Message) error {
city := msg.Args()
if city == "" {
return msg.Answer(m.T("no_city", "❌ <b>Какой город?</b>"))
}
data, err := m.fetch(city)
if err != nil {
return fmt.Errorf("получение погоды для %q: %w", city, err)
}
return msg.Answer(data)
}Два разных исхода:
msg.Answer(...)с понятным текстом — ожидаемая ситуация вроде отсутствующих аргументов. Возвращайтеnil.return err— что-то действительно сломалось. Goroku залогирует это и покажет пользователю❌ Ошибка выполнения команды: <ваш текст>.
Оборачивайте ошибки контекстом (fmt.Errorf("...: %w", err)) — именно этот текст вы будете читать в логе в три часа ночи.
Лимиты частоты
По умолчанию команды не ограничены. Включается на команду:
"weather": { Ratelimit: true },Диспетчер применит настроенные окна по пользователю и чату и сообщит пользователю о превышении.
Параллельность
Обработчики выполняются параллельно, каждый в своей горутине из ограниченного пула. Два следствия:
- Общее состояние на структуре модуля защищайте мьютексом. Модуль — один экземпляр на все чаты.
- Долгая работа допустима и не блокирует другие команды, но уважайте отмену через
msg.Context(), иначе завершение бота затянется.
type Weather struct {
goroku.Base
mu sync.Mutex
cache map[string]string
}
func (m *Weather) WeatherCmd(msg *goroku.Message) error {
m.mu.Lock()
cached, ok := m.cache[msg.Args()]
m.mu.Unlock()
if ok {
return msg.Answer(cached)
}
return m.fetchAndAnswer(msg)
}