-
-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0d95310
commit 9de994c
Showing
2 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
} | ||
} |