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 }