Reading input
Every handler receives a *goroku.Message. These helpers cover almost all argument parsing.
Args
Everything after the command word, trimmed:
// .say hello there
msg.Args() // "hello there"
// .say
msg.Args() // ""Inner spacing is preserved — .say a b gives "a b" — so it is safe for text you intend to echo verbatim.
ArgsList and Arg
Split on whitespace:
// .ban 42 spam here
msg.ArgsList() // []string{"42", "spam", "here"}
msg.Arg(0) // "42"
msg.Arg(1) // "spam"
msg.Arg(9) // "" — no panicArg is bounds-safe. You never need to check the length first:
func (m *Mod) BanCmd(msg *goroku.Message) error {
id := msg.Arg(0)
if id == "" {
return msg.Answer(m.T("no_id", "❌ <b>Who?</b>"))
}
reason := msg.Arg(1) // "" when omitted, which is fine
return m.ban(id, reason)
}ArgsOrReply
The pattern most commands want: use the typed arguments, or fall back to the message the user replied to.
func (m *HTML) HTMLCmd(msg *goroku.Message) error {
code := msg.ArgsOrReply()
if code == "" {
return msg.Answer(m.T("no_args", "❌ <b>Send text or reply</b>"))
}
return msg.Answer(code)
}Both of these now work:
.html <b>hi</b> → renders the typed markup
.html (as a reply) → renders the replied-to messageAn empty result means there was neither — treat it as "nothing to do".
The replied-to message
For anything beyond its text, fetch it:
reply, err := msg.GetReplyMessage()
if err != nil || reply == nil {
return msg.Answer(m.T("reply_needed", "❌ <b>Reply to a message</b>"))
}
if reply.Media != nil {
// handle the attachment
}To require a reply, prefer the declarative form — the dispatcher filters before your handler runs:
"mycmd": { OnlyReply: true },Message fields
| Field | Type | Meaning |
|---|---|---|
ID | int64 | Message ID |
ChatID | int64 | Where it was sent |
SenderID | int64 | Who sent it |
Text | string | Text, formatting applied |
RawText | string | Text as typed |
Out | bool | Sent by you |
IsPrivate, IsGroup, IsChannel | bool | Chat type |
Media | tg.MessageMediaClass | Attachment, nil when absent |
ReplyToMsgID | int64 | 0 when not a reply |
IsForwarded | bool | Forwarded from elsewhere |
Cancellation
msg.Context() is cancelled when the bot shuts down. Pass it to anything slow:
req, err := http.NewRequestWithContext(msg.Context(), http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)Handlers that ignore it still work — shutdown just waits for them.