docs: auto-generate markdown reference + soften README

- Add gomarkdoc-driven reference docs in docs/reference/, regenerated
  automatically by 'make regen' alongside the api/ codegen
- New 'make docs' target installs gomarkdoc on first run; 'make
  docs-check' is a CI gate
- Fold doc-clean assertion into existing codegen-clean job (single
  diff check covers spec + api + reference)
- Rewrite README header: logo via <picture>, friendlier tagline,
  emoji-led 'Why you'll like it' bullets instead of Why-table
- Drop duplicate echo snippet, soften 'Codegen pipeline' section into
  'Keeping up with Telegram'
- Link reference from README, Pages nav, and a new Markdown reference
  card on index.html (target = GitHub source view, renders .md natively)
This commit is contained in:
2026-05-09 14:11:28 +01:00
parent 35058dd70b
commit 1088b7f4d7
16 changed files with 16263 additions and 50 deletions
+243
View File
@@ -0,0 +1,243 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# conversation
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/conversation"
```
Package conversation implements a stateful conversation handler for the go\-telegram dispatch router. It provides a state\-machine abstraction over multi\-step Telegram bot interactions, with pluggable storage and flexible key strategies.
## Index
- [Variables](<#variables>)
- [func End\(\) error](<#End>)
- [func Next\(s State\) error](<#Next>)
- [type Conversation](<#Conversation>)
- [func \(c \*Conversation\) Dispatch\(next dispatch.Handler\[\*api.Update\]\) dispatch.Handler\[\*api.Update\]](<#Conversation.Dispatch>)
- [type Handler](<#Handler>)
- [type KeyStrategy](<#KeyStrategy>)
- [type MemoryStorage](<#MemoryStorage>)
- [func NewMemoryStorage\(\) \*MemoryStorage](<#NewMemoryStorage>)
- [func \(s \*MemoryStorage\) Delete\(\_ context.Context, key string\) error](<#MemoryStorage.Delete>)
- [func \(s \*MemoryStorage\) Get\(\_ context.Context, key string\) \(State, error\)](<#MemoryStorage.Get>)
- [func \(s \*MemoryStorage\) Set\(\_ context.Context, key string, state State\) error](<#MemoryStorage.Set>)
- [type State](<#State>)
- [type Step](<#Step>)
- [type Storage](<#Storage>)
## Variables
<a name="ErrKeyNotFound"></a>ErrKeyNotFound is returned by Storage.Get when no conversation is active for the given key.
```go
var ErrKeyNotFound = errors.New("conversation: key not found")
```
<a name="End"></a>
## func End
```go
func End() error
```
End signals the conversation has finished and state should be cleared. Conversation handlers return End\(\) to terminate.
<a name="Next"></a>
## func Next
```go
func Next(s State) error
```
Next signals the conversation should advance to the given state. Conversation handlers return Next\("state\_name"\) to transition.
<a name="Conversation"></a>
## type Conversation
Conversation is a stateful handler with entry, per\-state, exit and fallback steps. A conversation is keyed by KeyStrategy \(default KeyByUserAndChat\) and persisted by Storage \(default in\-memory\).
```go
type Conversation struct {
// EntryPoints starts a new conversation when a matching filter fires
// and no conversation is already active for the key.
EntryPoints []Step
// States maps each state to the steps that handle it.
States map[State][]Step
// Exits, if any match, end the active conversation early. Useful for
// /cancel-style commands.
Exits []Step
// Fallbacks run when no state step matches the current update.
Fallbacks []Step
// Storage persists conversation state. Defaults to NewMemoryStorage.
Storage Storage
// KeyStrategy derives the persistence key. Defaults to KeyByUserAndChat.
KeyStrategy KeyStrategy
// AllowReEntry, when true, lets entry-point steps fire even while a
// conversation is already active for the key (effectively restarting it).
AllowReEntry bool
}
```
<a name="Conversation.Dispatch"></a>
### func \(\*Conversation\) Dispatch
```go
func (c *Conversation) Dispatch(next dispatch.Handler[*api.Update]) dispatch.Handler[*api.Update]
```
Dispatch is a global middleware\-shaped Handler that consumes updates and routes them through the conversation graph. Register via router.Use\(conv.Dispatch\).
If the conversation claims an update, downstream handlers are skipped. If the conversation does not claim it, downstream handlers run as normal.
<a name="Handler"></a>
## type Handler
Handler defines a step in the conversation. Receives the dispatch context and the raw update. Returns:
- nil to stay in the current state
- Next\("state"\) to transition to a different state
- End\(\) to end the conversation
- any other non\-nil error to surface to the dispatcher \(state unchanged\)
```go
type Handler func(ctx *dispatch.Context, u *api.Update) error
```
<a name="KeyStrategy"></a>
## type KeyStrategy
KeyStrategy derives a persistence key from an update. Strategies determine how conversation scope works — per\-user, per\-chat, or per\-user\-and\-chat. Implementations must return a stable string for the same logical scope across updates.
Returns the empty string if the update doesn't have enough context to derive a key \(in which case the conversation handler skips it\).
```go
type KeyStrategy func(u *api.Update) string
```
<a name="KeyByChat"></a>KeyByChat derives a key from the chat ID. Useful for group flows where any user in the chat can drive the conversation.
```go
var KeyByChat KeyStrategy = func(u *api.Update) string {
if cid := chatID(u); cid != 0 {
return fmt.Sprintf("c:%d", cid)
}
return ""
}
```
<a name="KeyByUser"></a>KeyByUser derives a key from the sending user's ID. Useful for DM conversations and any flow that should follow the user across chats.
```go
var KeyByUser KeyStrategy = func(u *api.Update) string {
if uid := userID(u); uid != 0 {
return fmt.Sprintf("u:%d", uid)
}
return ""
}
```
<a name="KeyByUserAndChat"></a>KeyByUserAndChat derives a key from both user and chat IDs. The most common strategy: each user has their own conversation per chat.
```go
var KeyByUserAndChat KeyStrategy = func(u *api.Update) string {
uid := userID(u)
cid := chatID(u)
if uid == 0 || cid == 0 {
return ""
}
return fmt.Sprintf("uc:%d:%d", cid, uid)
}
```
<a name="MemoryStorage"></a>
## type MemoryStorage
MemoryStorage is the default in\-process Storage. It is safe for concurrent use. Conversation state is lost on process restart; use a custom Storage backed by a database for persistent flows.
```go
type MemoryStorage struct {
// contains filtered or unexported fields
}
```
<a name="NewMemoryStorage"></a>
### func NewMemoryStorage
```go
func NewMemoryStorage() *MemoryStorage
```
NewMemoryStorage constructs an empty in\-memory storage.
<a name="MemoryStorage.Delete"></a>
### func \(\*MemoryStorage\) Delete
```go
func (s *MemoryStorage) Delete(_ context.Context, key string) error
```
<a name="MemoryStorage.Get"></a>
### func \(\*MemoryStorage\) Get
```go
func (s *MemoryStorage) Get(_ context.Context, key string) (State, error)
```
<a name="MemoryStorage.Set"></a>
### func \(\*MemoryStorage\) Set
```go
func (s *MemoryStorage) Set(_ context.Context, key string, state State) error
```
<a name="State"></a>
## type State
State is a label identifying a node in the conversation graph. The empty string is the implicit "no active conversation" state.
```go
type State string
```
<a name="Step"></a>
## type Step
Step pairs a filter with a handler for one conversation step.
```go
type Step struct {
Filter dispatch.Filter[*api.Update]
Handler Handler
}
```
<a name="Storage"></a>
## type Storage
Storage persists per\-user \(or per\-chat, per\-message — depending on the KeyStrategy in use\) conversation state across update deliveries.
Implementations must be safe for concurrent use.
```go
type Storage interface {
Get(ctx context.Context, key string) (State, error)
Set(ctx context.Context, key string, state State) error
Delete(ctx context.Context, key string) error
}
```
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,55 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# callback
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/callback"
```
Package callback provides Filter helpers for \*api.CallbackQuery payloads.
## Index
- [func Data\(pattern string\) dispatch.Filter\[\*api.CallbackQuery\]](<#Data>)
- [func DataEquals\(s string\) dispatch.Filter\[\*api.CallbackQuery\]](<#DataEquals>)
- [func DataPrefix\(prefix string\) dispatch.Filter\[\*api.CallbackQuery\]](<#DataPrefix>)
- [func FromUser\(userID int64\) dispatch.Filter\[\*api.CallbackQuery\]](<#FromUser>)
<a name="Data"></a>
## func Data
```go
func Data(pattern string) dispatch.Filter[*api.CallbackQuery]
```
Data returns a Filter that matches callback queries whose Data matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="DataEquals"></a>
## func DataEquals
```go
func DataEquals(s string) dispatch.Filter[*api.CallbackQuery]
```
DataEquals returns a Filter that matches callback queries whose Data equals s exactly.
<a name="DataPrefix"></a>
## func DataPrefix
```go
func DataPrefix(prefix string) dispatch.Filter[*api.CallbackQuery]
```
DataPrefix returns a Filter that matches callback queries whose Data starts with prefix.
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(userID int64) dispatch.Filter[*api.CallbackQuery]
```
FromUser returns a Filter that matches callback queries whose From.ID equals userID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# chatjoinrequest
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatjoinrequest"
```
Package chatjoinrequest provides Filter helpers for \*api.ChatJoinRequest payloads.
## Index
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.ChatJoinRequest\]](<#FromUser>)
- [func InChat\(cid int64\) dispatch.Filter\[\*api.ChatJoinRequest\]](<#InChat>)
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatJoinRequest]
```
FromUser returns a Filter that matches join requests where the requesting user's ID equals uid.
<a name="InChat"></a>
## func InChat
```go
func InChat(cid int64) dispatch.Filter[*api.ChatJoinRequest]
```
InChat returns a Filter that matches join requests directed at the chat with the given chat ID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# chatmember
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatmember"
```
Package chatmember provides Filter helpers for \*api.ChatMemberUpdated payloads.
## Index
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.ChatMemberUpdated\]](<#FromUser>)
- [func NewStatus\(s string\) dispatch.Filter\[\*api.ChatMemberUpdated\]](<#NewStatus>)
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatMemberUpdated]
```
FromUser returns a Filter that matches updates where the acting user \(From.ID\) equals uid.
<a name="NewStatus"></a>
## func NewStatus
```go
func NewStatus(s string) dispatch.Filter[*api.ChatMemberUpdated]
```
NewStatus returns a Filter that matches updates where the new chat member status equals s \(e.g. "member", "administrator", "kicked", "left"\).
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+45
View File
@@ -0,0 +1,45 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# inline
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/inline"
```
Package inline provides Filter helpers for \*api.InlineQuery payloads.
## Index
- [func Query\(pattern string\) dispatch.Filter\[\*api.InlineQuery\]](<#Query>)
- [func QueryEquals\(s string\) dispatch.Filter\[\*api.InlineQuery\]](<#QueryEquals>)
- [func QueryPrefix\(prefix string\) dispatch.Filter\[\*api.InlineQuery\]](<#QueryPrefix>)
<a name="Query"></a>
## func Query
```go
func Query(pattern string) dispatch.Filter[*api.InlineQuery]
```
Query returns a Filter that matches inline queries whose Query field matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="QueryEquals"></a>
## func QueryEquals
```go
func QueryEquals(s string) dispatch.Filter[*api.InlineQuery]
```
QueryEquals returns a Filter that matches inline queries whose Query equals s exactly.
<a name="QueryPrefix"></a>
## func QueryPrefix
```go
func QueryPrefix(prefix string) dispatch.Filter[*api.InlineQuery]
```
QueryPrefix returns a Filter that matches inline queries whose Query starts with prefix.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+155
View File
@@ -0,0 +1,155 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# message
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/message"
```
Package message provides Filter helpers for \*api.Message payloads.
## Index
- [func AnyCommand\(\) dispatch.Filter\[\*api.Message\]](<#AnyCommand>)
- [func ChatType\(t api.ChatType\) dispatch.Filter\[\*api.Message\]](<#ChatType>)
- [func Command\(name string\) dispatch.Filter\[\*api.Message\]](<#Command>)
- [func FromUser\(userID int64\) dispatch.Filter\[\*api.Message\]](<#FromUser>)
- [func HasDocument\(\) dispatch.Filter\[\*api.Message\]](<#HasDocument>)
- [func HasEntity\(t string\) dispatch.Filter\[\*api.Message\]](<#HasEntity>)
- [func HasPhoto\(\) dispatch.Filter\[\*api.Message\]](<#HasPhoto>)
- [func InChat\(chatID int64\) dispatch.Filter\[\*api.Message\]](<#InChat>)
- [func IsForward\(\) dispatch.Filter\[\*api.Message\]](<#IsForward>)
- [func IsReply\(\) dispatch.Filter\[\*api.Message\]](<#IsReply>)
- [func Text\(pattern string\) dispatch.Filter\[\*api.Message\]](<#Text>)
- [func TextContains\(sub string\) dispatch.Filter\[\*api.Message\]](<#TextContains>)
- [func TextEquals\(s string\) dispatch.Filter\[\*api.Message\]](<#TextEquals>)
- [func TextPrefix\(prefix string\) dispatch.Filter\[\*api.Message\]](<#TextPrefix>)
<a name="AnyCommand"></a>
## func AnyCommand
```go
func AnyCommand() dispatch.Filter[*api.Message]
```
AnyCommand returns a Filter that matches any message starting with a bot\_command entity at offset 0.
<a name="ChatType"></a>
## func ChatType
```go
func ChatType(t api.ChatType) dispatch.Filter[*api.Message]
```
ChatType returns a Filter that matches messages whose Chat.Type equals t.
<a name="Command"></a>
## func Command
```go
func Command(name string) dispatch.Filter[*api.Message]
```
Command returns a Filter that matches messages whose first entity is a bot\_command equal to "/\<name\>" \(with or without "@BotName" suffix\).
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(userID int64) dispatch.Filter[*api.Message]
```
FromUser returns a Filter that matches messages whose From.ID equals userID.
<a name="HasDocument"></a>
## func HasDocument
```go
func HasDocument() dispatch.Filter[*api.Message]
```
HasDocument returns a Filter that matches messages with a Document attachment.
<a name="HasEntity"></a>
## func HasEntity
```go
func HasEntity(t string) dispatch.Filter[*api.Message]
```
HasEntity returns a Filter that matches messages whose Entities contain at least one entity of type t \(e.g. string\(api.EntityBotCommand\)\).
<a name="HasPhoto"></a>
## func HasPhoto
```go
func HasPhoto() dispatch.Filter[*api.Message]
```
HasPhoto returns a Filter that matches messages with a Photo attachment.
<a name="InChat"></a>
## func InChat
```go
func InChat(chatID int64) dispatch.Filter[*api.Message]
```
InChat returns a Filter that matches messages whose Chat.ID equals chatID.
<a name="IsForward"></a>
## func IsForward
```go
func IsForward() dispatch.Filter[*api.Message]
```
IsForward returns a Filter that matches messages that have ForwardOrigin set.
<a name="IsReply"></a>
## func IsReply
```go
func IsReply() dispatch.Filter[*api.Message]
```
IsReply returns a Filter that matches messages that have ReplyToMessage set.
<a name="Text"></a>
## func Text
```go
func Text(pattern string) dispatch.Filter[*api.Message]
```
Text returns a Filter that matches messages whose Text matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="TextContains"></a>
## func TextContains
```go
func TextContains(sub string) dispatch.Filter[*api.Message]
```
TextContains returns a Filter that matches messages whose Text contains sub.
<a name="TextEquals"></a>
## func TextEquals
```go
func TextEquals(s string) dispatch.Filter[*api.Message]
```
TextEquals returns a Filter that matches messages whose Text equals s exactly.
<a name="TextPrefix"></a>
## func TextPrefix
```go
func TextPrefix(prefix string) dispatch.Filter[*api.Message]
```
TextPrefix returns a Filter that matches messages whose Text starts with prefix.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# precheckoutquery
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/precheckoutquery"
```
Package precheckoutquery provides Filter helpers for \*api.PreCheckoutQuery payloads.
## Index
- [func Currency\(c string\) dispatch.Filter\[\*api.PreCheckoutQuery\]](<#Currency>)
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.PreCheckoutQuery\]](<#FromUser>)
<a name="Currency"></a>
## func Currency
```go
func Currency(c string) dispatch.Filter[*api.PreCheckoutQuery]
```
Currency returns a Filter that matches pre\-checkout queries with the given ISO 4217 currency code \(e.g. "USD", "EUR", "XTR"\).
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.PreCheckoutQuery]
```
FromUser returns a Filter that matches pre\-checkout queries sent by the user with the given ID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)