Skip to content

Befehle

Registrieren

go
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:

go
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.

go
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:

FeldWirkung
OnlyOwnerNur der Kontobesitzer
OnlyPM / NoPMNur / nie in privaten Chats
OnlyGroups, OnlyChannels, OnlyChatsNach Chattyp einschränken
OnlyReply / NoReplyAntwortnachricht verlangen / verbieten
OnlyMedia, OnlyPhotos, OnlyVideos, OnlyDocsAnhang verlangen
NoMedia, NoStickers, NoAudio, NoDocAnhänge ablehnen
RatelimitRatenlimits pro Nutzer und Chat anwenden
Filterfunc(*Message) bool für alles Übrige

Die vollständige Liste steht unter CommandMeta.

Auf Inhalt prüfen

go
"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:

go
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. Gib nil zurü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:

go
"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.
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)
}

Released under the GNU AGPL v3 License.