Skip to content

Getting started

A bot that reads a Discord channel and answers in a thread, without a Discord SDK anywhere in your own code.

Install

The contract and the provider are separate modules. You depend on the contract; the provider is a blank import.

go get gitlab.com/phpboyscout/go/chat-platform
go get gitlab.com/phpboyscout/go/chat-platform-discord

Connect and read

package main

import (
    "context"
    "log"
    "os"

    chatplatform "gitlab.com/phpboyscout/go/chat-platform"
    _ "gitlab.com/phpboyscout/go/chat-platform-discord"
)

func main() {
    factory, ok := chatplatform.Lookup("discord")
    if !ok {
        log.Fatal("no discord provider registered")
    }

    p, err := factory(chatplatform.Config{
        Token:           os.Getenv("DISCORD_TOKEN"),
        Space:           "1531227937678954747",
        AllowedChannels: []chatplatform.ID{"1531227938622800055"},
    })
    if err != nil {
        log.Fatal(err)
    }

    defer p.Reader.Close()

    ctx := context.Background()

    if err := p.Reader.Connect(ctx); err != nil {
        log.Fatal(err)
    }

    for msg := range p.Reader.Messages() {
        if _, err := p.Actor.ReplyInThread(ctx, msg.Ref(), "Re: "+msg.Author.Name, "Looking into it."); err != nil {
            log.Print(err)
        }
    }
}

Three things are worth noticing.

Lookup takes a string. The provider registered itself when it was imported. Swapping platforms is a configuration change and an import, not a code change.

AllowedChannels is enforced by the provider. An empty allowlist permits nothing. A watchlist that silently means everywhere is the wrong default for reading people's messages, so it fails closed.

Connect is where the network happens. factory validates configuration and nothing else, so you can construct a provider, inspect what it supports, and test all of it without credentials.

Read without being able to write

Set ReadOnly and the provider comes back with no Actor at all.

p, _ := factory(chatplatform.Config{
    Token:           os.Getenv("DISCORD_TOKEN"),
    Space:           guild,
    AllowedChannels: channels,
    ReadOnly:        true,
})

p.Actor == nil // always

There is no flag to check and no method that refuses at call time. A deployment that cannot post cannot delete a message either, and that is a property of the types rather than of a check somebody remembers to write.

Use a capability

Moderation, member lookup, interactive components and slash commands are optional. Ask for them by type assertion:

if mod, ok := chatplatform.AsModerator(p); ok {
    err := mod.DeleteMessage(ctx, ref, "off-topic")
}

A provider that has none of them is legitimate, which is why they are not part of Actor. See Optional capabilities.

Notice a reconnect that lost messages

if st := p.Reader.State(); st.LastReconnectLostEvents {
    log.Print("reconnect re-identified; messages during the gap were dropped")
}

This is the failure worth alerting on, and it is invisible without being told. See Reconnects and lost events.

Next