gatewayutil

package
v0.2.0-beta Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2023 License: MIT Imports: 13 Imported by: 0

README

Gatewayutil

Utility package for gateway

Simple shard example

This code uses github.com/gobwas/ws, but you are free to use other websocket implementations as well. You just have to write your own Shard implementation and use gateway.Client. See gatewayutil/shard.go for inspiration.

Create a shard instance using the gatewayutil package:

package main

import (
   "context"
   "errors"
   "fmt"
   "github.com/discordpkg/gateway"
   "github.com/discordpkg/gateway/event"
   "github.com/discordpkg/gateway/intent"
   "github.com/discordpkg/gateway/log"
   "github.com/discordpkg/gateway/gatewayutil"
   "net"
   "os"
)

func main() {
   shard, err := gatewayutil.NewShard(
      // gateway.WithLogger(&printLogger{}),
      gateway.WithBotToken(os.Getenv("DISCORD_TOKEN")),
      // gateway.WithEventHandler(someEventHandler),
      gateway.WithShardInfo(0, 1),
      gateway.WithGuildEvents(event.All()...),
      gateway.WithDirectMessageEvents(event.All()...),
      gateway.WithCommandRateLimiter(gatewayutil.NewCommandRateLimiter()),
      gateway.WithIdentifyRateLimiter(gatewayutil.NewLocalIdentifyRateLimiter()),
      gateway.WithIdentifyConnectionProperties(&gateway.IdentifyConnectionProperties{
         OS:      "linux",
         Browser: "github.com/discordpkg/gateway v0",
         Device:  "tester",
      }),
   )
   if err != nil {
      panic(err)
   }

You can then open a connection to discord and start listening for events. The event loop will continue to run until the connection is lost or a process failed (json unmarshal/marshal, websocket frame issue, etc.)

You can use the helper methods for the DiscordError to decide when to reconnect:

reconnectStage:
   _, err := shard.Dial(context.Background(), func() (string, error) {
      // code for calling GetGatewayBot url, only called if no resume url was cached from Discord 
	  return "wss://gateway.discord.gg/?v=10&encoding=json"
   })
   if err != nil {
      log.Fatal("failed to open websocket connection. ", err)
   }

   if err = shard.EventLoop(context.Background()); err != nil {
      var discordErr *gateway.DiscordError
      if errors.As(err, &discordErr) && discordErr.CanReconnect() {
         goto reconnectStage
      }
	  
	  return err
   }
}

Or manually check the close code, operation code, or error:

   err := shard.EventLoop(context.Background()); 
   if err != nil {
      var discordErr *gateway.DiscordError
      if errors.As(err, &discordErr) {
         switch discordErr.CloseCode {
         case 1001, 4000, 4007, 4009:
            // use reconnect logic defined later
         case 4001, 4002, 4003, 4004, 4005, 4008, 4010, 4011, 4012, 4013, 4014:
            log.Fatal("an error occured:", err)
         default:
            log.Fatal(fmt.Errorf("unhandled close error, with discord op code(%d): %d", op, discordErr.Code))
         }
      } else if !errors.Is(err, net.ErrClosed) {
         logger.Fatal("an error occured:", err)
      }
   }   
   
   goto reconnectStage
}

Gateway command

To request guild members, update voice state or update presence, you can utilize Shard.Write or GatewayState.Write (same logic). The bytes argument should not contain the discord payload wrapper (operation code, event name, etc.), instead you write only the inner object and specify the relevant operation code.

Calling Write(..) before dial or instantiating a net.Conn object will cause the process to fail. You must be connected.

package main

import (
   "context"
   "fmt"
   "github.com/discordpkg/gateway"
   "github.com/discordpkg/gateway/intent"
   "github.com/discordpkg/gateway/event"
   "github.com/discordpkg/gateway/gatewayutil"
   "os"
)

func main() {
   shard, err := gatewayutil.NewShard(
      // gateway.WithLogger(&printLogger{}),
      gateway.WithBotToken(os.Getenv("DISCORD_TOKEN")),
      // gateway.WithEventHandler(someEventHandler),
      gateway.WithShardInfo(0, 1),
      gateway.WithGuildEvents(event.All()...),
      gateway.WithDirectMessageEvents(event.All()...),
      gateway.WithCommandRateLimiter(gatewayutil.NewCommandRateLimiter()),
      gateway.WithIdentifyRateLimiter(gatewayutil.NewLocalIdentifyRateLimiter()),
      gateway.WithIntents(intent.Guilds),
      gateway.WithCommandRateLimiter(gatewayutil.NewCommandRateLimiter()),
      gateway.WithIdentifyRateLimiter(gatewayutil.NewLocalIdentifyRateLimiter()),
   )
   if err != nil {
      panic(err)
   }

   if _, err := shard.Dial(context.Background(), client.GetGatewayBotURL); err != nil {
      panic(fmt.Errorf("failed to open websocket connection. ", err))
   }

   // ...

   req := `{"guild_id":"23423","limit":0,"query":""}`
   if err := shard.Write(event.RequestGuildMembers, []byte(req)); err != nil {
      panic(fmt.Errorf("failed to request guild members", err))
   }
}

If you need to manually set the intent value for whatever reason, the ShardConfig exposes an "Intents" field. Note that intents will still be derived from DMEvents and GuildEvents and added to the final intents value used to identify.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrIncompleteDialURL = errors.New("incomplete url is missing one or many of: 'version', 'encoding', 'scheme'")
View Source
var ErrURLScheme = errors.New("url scheme was not websocket (ws nor wss)")
View Source
var ErrUnsupportedAPICodec = fmt.Errorf("only %+v is supported", supportedAPICodes)
View Source
var ErrUnsupportedAPIVersion = fmt.Errorf("only discord api version %+v is supported", supportedAPIVersions)

Functions

func DeriveShardID

func DeriveShardID(snowflake uint64, totalNumberOfShards uint) gateway.ShardID

func ValidateDialURL

func ValidateDialURL(URLString string) (string, error)

Types

type GetGatewayBotURL

type GetGatewayBotURL func() (string, error)

type LocalCommandRateLimiter

type LocalCommandRateLimiter struct {
	// contains filtered or unexported fields
}

func NewCommandRateLimiter

func NewCommandRateLimiter() *LocalCommandRateLimiter

func (*LocalCommandRateLimiter) Try

type LocalIdentifyRateLimiter

type LocalIdentifyRateLimiter struct {
	// contains filtered or unexported fields
}

func NewLocalIdentifyRateLimiter

func NewLocalIdentifyRateLimiter() *LocalIdentifyRateLimiter

func (*LocalIdentifyRateLimiter) Try

type Shard

type Shard struct {
	Conn net.Conn
	// contains filtered or unexported fields
}

func NewShard

func NewShard(options ...gateway.Option) (*Shard, error)

func (*Shard) Dial

func (s *Shard) Dial(ctx context.Context, getURL GetGatewayBotURL) (connection net.Conn, err error)

Dial sets up the websocket connection before identifying with the gateway. The url must be complete and specify api version and encoding:

"wss://gateway.discord.gg/"                      => invalid
"wss://gateway.discord.gg/?v=10"                 => invalid
"wss://gateway.discord.gg/?v=10&encoding=json"   => valid

func (*Shard) EventLoop

func (s *Shard) EventLoop(ctx context.Context) error

func (*Shard) Write

func (s *Shard) Write(op event.Type, data []byte) error

type WebsocketError

type WebsocketError struct {
	Err error
}

func (*WebsocketError) Error

func (e *WebsocketError) Error() string

func (*WebsocketError) Unwrap

func (e *WebsocketError) Unwrap() error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL