I’m a huge fan of Matrix. A lot of the user value of modern chat platforms like Slack, Matrix and Discord (even IRC) comes from integrations to other services via bots. I had high hopes for MSC3006: Bot Interactions, but unfortunately it isn’t currently being pushed further. However, there exists an implementation of MSC3381: Polls.
Early poll bots used reactions for interaction instead of traditional text
commands. Now that there exists an MSC for polls with implementations in the
Element clients, I realized it can be used as a menu-driven interface, like
dialog(1), whiptail(1) or zenity(1).
You can try out the bot by opening a new chat with @menubot:matrix.org. Please be aware that federation latency may affect the snappiness of your experience.
The code is available on gitlab. I don’t claim that it’s pretty, or even good, but it does seem to work.
Some notes on how I used the excellent mautrix-go library to achieve this:
Mautrix-go doesn’t have support for all experimental MSCs, or probably even everything that’s in the stable specification. In particular it took me a while to realize that the library was ignoring the poll related events. In order to log unknown events, I added:
syncer.ParseErrorHandler = func(e *event.Event, err error) bool {
log.Printf("unknown type %+v", e)
return false
}
Being aware of the needed types, I started adding them:
var (
EventPollStart = event.Type{
"org.matrix.msc3381.poll.start",
event.MessageEventType,
}
EventPollResponse = event.Type{
"org.matrix.msc3381.poll.response",
event.MessageEventType,
}
EventPollEnd = event.Type{
"org.matrix.msc3381.poll.end",
event.MessageEventType,
}
)
And corresponding structs:
type PollKind string
const (
KindDisclosed PollKind = "org.matrix.msc3381.poll.disclosed"
KindUndisclosed PollKind = "org.matrix.msc3381.poll.undisclosed"
)
type PollQuestion struct {
FallbackText string `json:"org.matrix.msc1767.text,omitempty"`
Body string `json:"body"`
MsgType event.MessageType `json:"msgtype"`
}
type PollAnswer struct {
ID string `json:"id"`
Text string `json:"org.matrix.msc1767.text"`
}
type PollStart struct {
Question PollQuestion `json:"question"`
Kind PollKind `json:"kind"`
MaxSelections int `json:"max_selections"`
Answers []PollAnswer `json:"answers"`
FallbackText string `json:"org.matrix.msc1767.text,omitempty"`
}
type PollStartContent struct {
PollStart PollStart `json:"org.matrix.msc3381.poll.start"`
}
type PollResponse struct {
Answers []string `json:"answers"`
}
type PollResponseContent struct {
PollResponse PollResponse `json:"org.matrix.msc3381.poll.response"`
RelatesTo *event.RelatesTo `json:"m.relates_to"`
}
type PollEndContent struct {
PollEnd map[string]string `json:"org.matrix.msc3381.poll.end"`
FallbackText string `json:"org.matrix.msc1767.text,omitempty"`
Body string `json:"body"`
RelatesTo *event.RelatesTo `json:"m.relates_to"`
MsgType event.MessageType `json:"msgtype"`
}
Those still had to be hooked up to the library via the TypeMap:
event.TypeMap[EventPollResponse] = reflect.TypeOf(PollResponseContent{})
event.TypeMap[EventPollStart] = reflect.TypeOf(PollStartContent{})
event.TypeMap[EventPollEnd] = reflect.TypeOf(PollEndContent{})
That finally allowed me to write the code for creating a poll and responding appropriately to the user interacting with the poll.