github.com/diamondburned/arikawa/v2@v2.1.0/gateway/op.go (about)

     1  package gateway
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"time"
     8  
     9  	"github.com/diamondburned/arikawa/v2/utils/json"
    10  	"github.com/diamondburned/arikawa/v2/utils/wsutil"
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  type OPCode = wsutil.OPCode
    15  
    16  const (
    17  	DispatchOP            OPCode = 0 // recv
    18  	HeartbeatOP           OPCode = 1 // send/recv
    19  	IdentifyOP            OPCode = 2 // send...
    20  	StatusUpdateOP        OPCode = 3 //
    21  	VoiceStateUpdateOP    OPCode = 4 //
    22  	VoiceServerPingOP     OPCode = 5 //
    23  	ResumeOP              OPCode = 6 //
    24  	ReconnectOP           OPCode = 7 // recv
    25  	RequestGuildMembersOP OPCode = 8 // send
    26  	InvalidSessionOP      OPCode = 9 // recv...
    27  	HelloOP               OPCode = 10
    28  	HeartbeatAckOP        OPCode = 11
    29  	CallConnectOP         OPCode = 13
    30  	GuildSubscriptionsOP  OPCode = 14
    31  )
    32  
    33  // ErrReconnectRequest is returned by HandleOP if a ReconnectOP is given. This
    34  // is used mostly internally to signal the heartbeat loop to reconnect, if
    35  // needed. It is not a fatal error.
    36  var ErrReconnectRequest = errors.New("ReconnectOP received")
    37  
    38  func (g *Gateway) HandleOP(op *wsutil.OP) error {
    39  	switch op.Code {
    40  	case HeartbeatAckOP:
    41  		// Heartbeat from the server?
    42  		g.PacerLoop.Echo()
    43  
    44  	case HeartbeatOP:
    45  		ctx, cancel := context.WithTimeout(context.Background(), g.WSTimeout)
    46  		defer cancel()
    47  
    48  		// Server requesting a heartbeat.
    49  		if err := g.PacerLoop.Pace(ctx); err != nil {
    50  			return wsutil.ErrBrokenConnection(errors.Wrap(err, "failed to pace"))
    51  		}
    52  
    53  	case ReconnectOP:
    54  		// Server requests to Reconnect, die and retry.
    55  		wsutil.WSDebug("ReconnectOP received.")
    56  
    57  		// Exit with the ReconnectOP error to force the heartbeat event loop to
    58  		// Reconnect synchronously. Not really a fatal error.
    59  		return wsutil.ErrBrokenConnection(ErrReconnectRequest)
    60  
    61  	case InvalidSessionOP:
    62  		// Discord expects us to sleep for no reason
    63  		time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
    64  
    65  		ctx, cancel := context.WithTimeout(context.Background(), g.WSTimeout)
    66  		defer cancel()
    67  
    68  		// Invalid session, try and Identify.
    69  		if err := g.IdentifyCtx(ctx); err != nil {
    70  			// Can't identify, Reconnect.
    71  			return wsutil.ErrBrokenConnection(ErrReconnectRequest)
    72  		}
    73  
    74  		return nil
    75  
    76  	case HelloOP:
    77  		return nil
    78  
    79  	case DispatchOP:
    80  		// Set the sequence
    81  		if op.Sequence > 0 {
    82  			g.Sequence.Set(op.Sequence)
    83  		}
    84  
    85  		// Check if we know the event
    86  		fn, ok := EventCreator[op.EventName]
    87  		if !ok {
    88  			return fmt.Errorf(
    89  				"unknown event %s: %s",
    90  				op.EventName, string(op.Data),
    91  			)
    92  		}
    93  
    94  		// Make a new pointer to the event
    95  		var ev = fn()
    96  
    97  		// Try and parse the event
    98  		if err := json.Unmarshal(op.Data, ev); err != nil {
    99  			return errors.Wrap(err, "failed to parse event "+op.EventName)
   100  		}
   101  
   102  		// If the event is a ready, we'll want its sessionID
   103  		if ev, ok := ev.(*ReadyEvent); ok {
   104  			g.sessionMu.Lock()
   105  			g.sessionID = ev.SessionID
   106  			g.sessionMu.Unlock()
   107  		}
   108  
   109  		// Throw the event into a channel; it's valid now.
   110  		g.Events <- ev
   111  		return nil
   112  
   113  	default:
   114  		return fmt.Errorf("unknown OP code %d (event %s)", op.Code, op.EventName)
   115  	}
   116  
   117  	return nil
   118  }