Skip to content

Commit

Permalink
examples: Add commands-hybrid
Browse files Browse the repository at this point in the history
  • Loading branch information
diamondburned committed Aug 23, 2022
1 parent 0d95310 commit 9de994c
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 0 deletions.
23 changes: 23 additions & 0 deletions 0-examples/commands-hybrid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# commands-hybrid

commands-hybrid is an alternative variant of commands, where the program permits
being hosted either as a Gateway-based daemon or as a web server using the
Interactions Webhook API.

## Usage

### Gateway Mode

```sh
BOT_TOKEN="<token here>" go run .
```

### Interactions Webhook Mode

```sh
BOT_TOKEN="<token here>" WEBHOOK_ADDR="localhost:29485" WEBHOOK_PUBKEY="<hex app pubkey>" go run .
```

The endpoint will be `http://localhost:29485/`. I recommend using something like
[srv.us](https://srv.us) to expose this endpoint as a public one, which can then
be used by Discord.
178 changes: 178 additions & 0 deletions 0-examples/commands-hybrid/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package main

import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"time"

"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/api/webhook"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/state"
"github.com/diamondburned/arikawa/v3/utils/json/option"
"github.com/pkg/errors"
)

func main() {
token := os.Getenv("BOT_TOKEN")
if token == "" {
log.Fatalln("No $BOT_TOKEN given.")
}

var h handler

var (
webhookAddr = os.Getenv("WEBHOOK_ADDR")
webhookPubkey = os.Getenv("WEBHOOK_PUBKEY")
)

if webhookAddr != "" {
h.s = state.NewAPIOnlyState(token, nil)

srv, err := webhook.NewInteractionServer(webhookPubkey, &h)
if err != nil {
log.Fatalln("cannot create interaction server:", err)
}

if err := overwriteCommands(h.s); err != nil {
log.Fatalln("cannot update commands:", err)
}

log.Println("listening and serving at", webhookAddr+"/")
log.Fatalln(http.ListenAndServe(webhookAddr, srv))
} else {
h.s = state.New("Bot " + token)
h.s.AddInteractionHandler(&h)
h.s.AddIntents(gateway.IntentGuilds)
h.s.AddHandler(func(*gateway.ReadyEvent) {
me, _ := h.s.Me()
log.Println("connected to the gateway as", me.Tag())
})

if err := overwriteCommands(h.s); err != nil {
log.Fatalln("cannot update commands:", err)
}

if err := h.s.Connect(context.Background()); err != nil {
log.Fatalln("cannot connect:", err)
}
}
}

var commands = []api.CreateCommandData{
{
Name: "ping",
Description: "ping pong!",
},
{
Name: "echo",
Description: "echo back the argument",
Options: []discord.CommandOption{
&discord.StringOption{
OptionName: "argument",
Description: "what's echoed back",
Required: true,
},
},
},
{
Name: "thonk",
Description: "biiiig thonk",
},
}

func overwriteCommands(s *state.State) error {
app, err := s.CurrentApplication()
if err != nil {
return errors.Wrap(err, "cannot get current app ID")
}

_, err = s.BulkOverwriteCommands(app.ID, commands)
return err
}

type handler struct {
s *state.State
}

func (h *handler) HandleInteraction(ev *discord.InteractionEvent) *api.InteractionResponse {
switch data := ev.Data.(type) {
case *discord.CommandInteraction:
switch data.Name {
case "ping":
return h.cmdPing(ev, data)
case "echo":
return h.cmdEcho(ev, data)
case "thonk":
return h.cmdThonk(ev, data)
default:
return errorResponse(fmt.Errorf("unknown command %q", data.Name))
}
default:
return errorResponse(fmt.Errorf("unknown interaction %T", ev.Data))
}
}

func (h *handler) cmdPing(ev *discord.InteractionEvent, _ *discord.CommandInteraction) *api.InteractionResponse {
return &api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("Pong!"),
},
}
}

func (h *handler) cmdEcho(ev *discord.InteractionEvent, data *discord.CommandInteraction) *api.InteractionResponse {
var options struct {
Arg string `discord:"argument"`
}

if err := data.Options.Unmarshal(&options); err != nil {
return errorResponse(err)
}

return &api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: option.NewNullableString(options.Arg),
AllowedMentions: &api.AllowedMentions{},
},
}
}

func (h *handler) cmdThonk(ev *discord.InteractionEvent, data *discord.CommandInteraction) *api.InteractionResponse {
go func() {
time.Sleep(time.Duration(3+rand.Intn(5)) * time.Second)

h.s.FollowUpInteraction(ev.AppID, ev.Token, api.InteractionResponseData{
Content: option.NewNullableString("https://tenor.com/view/thonk-thinking-sun-thonk-sun-thinking-sun-gif-14999983"),
})
}()

return deferResponse(0)
}

func errorResponse(err error) *api.InteractionResponse {
return &api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("**Error:** " + err.Error()),
Flags: discord.EphemeralMessage,
AllowedMentions: &api.AllowedMentions{ /* none */ },
},
}
}

func deferResponse(flags discord.MessageFlags) *api.InteractionResponse {
return &api.InteractionResponse{
Type: api.DeferredMessageInteractionWithSource,
Data: &api.InteractionResponseData{
Flags: flags,
},
}
}

0 comments on commit 9de994c

Please sign in to comment.