Befehle
Registrieren
func (m *Weather) Commands() map[string]goroku.CommandHandler {
return map[string]goroku.CommandHandler{
"weather": m.WeatherCmd,
}
}Der Map-Schlüssel ist das Befehlswort. Mit dem Standardpräfix . registriert das .weather. Groß- und Kleinschreibung spielt keine Rolle.
Befehls- und Aliasnamen sind über alle geladenen Module hinweg eindeutig. Gehört weather schon einem anderen Modul, scheitert die Registrierung mit einem Kollisionsfehler, der den Konflikt benennt, statt ihn stillschweigend zu überschreiben.
Aliase
Ergänze CommandMetas — ein optionales Interface, die Deklaration genügt:
func (m *Weather) CommandMetas() map[string]goroku.CommandMeta {
return map[string]goroku.CommandMeta{
"weather": {
Aliases: []string{"w", "wx"},
},
}
}Jetzt funktionieren .weather, .w und .wx.
Einschränken, wo ein Befehl läuft
Die Felder von CommandMeta sind Filter. Der Dispatcher prüft sie, bevor dein Handler läuft — ein unpassender Befehl löst also einfach nicht aus.
func (m *Weather) CommandMetas() map[string]goroku.CommandMeta {
return map[string]goroku.CommandMeta{
"weather": {
Aliases: []string{"w"},
OnlyOwner: true, // nur du
OnlyGroups: true, // nur in Gruppen
NoForwarded: true, // weitergeleitete ignorieren
},
}
}Häufig gebraucht:
| Feld | Wirkung |
|---|---|
OnlyOwner | Nur der Kontobesitzer |
OnlyPM / NoPM | Nur / nie in privaten Chats |
OnlyGroups, OnlyChannels, OnlyChats | Nach Chattyp einschränken |
OnlyReply / NoReply | Antwortnachricht verlangen / verbieten |
OnlyMedia, OnlyPhotos, OnlyVideos, OnlyDocs | Anhang verlangen |
NoMedia, NoStickers, NoAudio, NoDoc | Anhänge ablehnen |
Ratelimit | Ratenlimits pro Nutzer und Chat anwenden |
Filter | func(*Message) bool für alles Übrige |
Die vollständige Liste steht unter CommandMeta.
Auf Inhalt prüfen
"weather": {
StartsWith: "stadt ",
Regex: `^\d{5}$`,
},Regex wird einmal bei der Registrierung kompiliert. Ein ungültiges Muster lässt die Registrierung scheitern, statt still nie zu greifen.
Fehler
Gib einen Fehler zurück, und der Nutzer sieht ihn:
func (m *Weather) WeatherCmd(msg *goroku.Message) error {
city := msg.Args()
if city == "" {
return msg.Answer(m.T("no_city", "❌ <b>Welche Stadt?</b>"))
}
data, err := m.fetch(city)
if err != nil {
return fmt.Errorf("Wetter für %q abrufen: %w", city, err)
}
return msg.Answer(data)
}Zwei verschiedene Ausgänge:
msg.Answer(...)mit freundlichem Text — eine erwartbare Lage wie fehlende Argumente. Gibnilzurück.return err— tatsächlich ist etwas schiefgegangen. Goroku loggt es und zeigt dem Nutzer❌ Fehler bei der Befehlsausführung: <dein Text>.
Umschließe Fehler mit Kontext (fmt.Errorf("...: %w", err)); genau diesen Text liest du um drei Uhr nachts im Log.
Ratenlimits
Befehle sind standardmäßig nicht limitiert. Pro Befehl aktivieren:
"weather": { Ratelimit: true },Der Dispatcher wendet dann die konfigurierten Fenster pro Nutzer und Chat an und sagt dem Nutzer Bescheid, wenn er darüber liegt.
Nebenläufigkeit
Handler laufen nebenläufig, jeder in einer eigenen Goroutine aus einem begrenzten Pool. Zwei Folgen:
- Gemeinsamen Zustand am Modul-Struct mit einem Mutex schützen. Dein Modul ist eine einzige Instanz für alle Chats.
- Lange Arbeit ist in Ordnung und blockiert andere Befehle nicht, aber achte auf Abbruch über
msg.Context(), damit das Herunterfahren nicht verzögert wird.
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)
}