コマンド
登録する
go
func (m *Weather) Commands() map[string]goroku.CommandHandler {
return map[string]goroku.CommandHandler{
"weather": m.WeatherCmd,
}
}マップのキーがコマンド語です。既定のプレフィックス . では .weather が登録され ます。キーの大文字小文字は区別しません。
コマンド名とエイリアスは、読み込み済みのすべてのモジュールを通じて一意です。 weather を別のモジュールが既に持っていれば、登録は衝突エラーで失敗し、どこが ぶつかったかを示します。黙って上書きすることはありません。
エイリアス
CommandMetas を足します。任意インターフェースなので、宣言するだけで有効です。
go
func (m *Weather) CommandMetas() map[string]goroku.CommandMeta {
return map[string]goroku.CommandMeta{
"weather": {
Aliases: []string{"w", "wx"},
},
}
}これで .weather、.w、.wx がすべて動きます。
実行できる場所を絞る
CommandMeta のフィールドはフィルタです。ディスパッチャがハンドラの実行前に検査 するので、当てはまらないコマンドはそもそも発火しません。
go
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 にあります。
内容で照合する
go
"weather": {
StartsWith: "都市 ",
Regex: `^\d{5}$`,
},Regex は登録時に一度だけコンパイルされます。不正なパターンは、黙って一致しない のではなく登録を失敗させます。
エラー
エラーを返せば利用者に表示されます。
go
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)
}結果は2通りです。
- 分かりやすい文言での
msg.Answer(...)— 引数不足のような想定内の状況。nilを返します。 return err— 実際に失敗した場合。Goroku がログに記録し、利用者には❌ コマンド実行エラー: <あなたの文言>を表示します。
エラーは文脈で包んでください(fmt.Errorf("...: %w", err))。深夜3時にログで読む のはその文字列です。
レート制限
コマンドは既定では制限されません。コマンド単位で有効化します。
go
"weather": { Ratelimit: true },ディスパッチャが設定済みの利用者別・チャット別ウィンドウを適用し、超過を利用者に 知らせます。
並行性
ハンドラは上限付きプールのゴルーチンで並行に動きます。帰結は2つ。
- モジュール構造体の共有状態は mutex で守ってください。モジュールは全チャットで 1つのインスタンスです。
- 時間のかかる処理は他のコマンドをブロックしないので問題ありませんが、
msg.Context()によるキャンセルは尊重してください。さもないと終了が遅れます。
go
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)
}