2020-04-25 02:36:33 +00:00
|
|
|
package voicegateway
|
2020-04-20 01:21:10 +00:00
|
|
|
|
|
|
|
import (
|
2020-07-11 19:50:32 +00:00
|
|
|
"context"
|
2020-04-20 01:21:10 +00:00
|
|
|
"time"
|
|
|
|
|
2020-10-28 22:39:59 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/discord"
|
2020-04-25 02:36:33 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrMissingForIdentify is an error when we are missing information to identify.
|
|
|
|
ErrMissingForIdentify = errors.New("missing GuildID, UserID, SessionID, or Token for identify")
|
|
|
|
|
|
|
|
// ErrMissingForResume is an error when we are missing information to resume.
|
|
|
|
ErrMissingForResume = errors.New("missing GuildID, SessionID, or Token for resuming")
|
2020-04-20 01:21:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// OPCode 0
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload
|
2020-04-20 01:21:10 +00:00
|
|
|
type IdentifyData struct {
|
2020-07-21 20:27:59 +00:00
|
|
|
GuildID discord.GuildID `json:"server_id"` // yes, this should be "server_id"
|
|
|
|
UserID discord.UserID `json:"user_id"`
|
|
|
|
SessionID string `json:"session_id"`
|
|
|
|
Token string `json:"token"`
|
2020-04-20 01:21:10 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 02:36:33 +00:00
|
|
|
// Identify sends an Identify operation (opcode 0) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) Identify() error {
|
2020-07-11 19:50:32 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return c.IdentifyCtx(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdentifyCtx sends an Identify operation (opcode 0) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) IdentifyCtx(ctx context.Context) error {
|
2020-04-25 02:36:33 +00:00
|
|
|
guildID := c.state.GuildID
|
|
|
|
userID := c.state.UserID
|
|
|
|
sessionID := c.state.SessionID
|
|
|
|
token := c.state.Token
|
2020-04-20 01:21:10 +00:00
|
|
|
|
|
|
|
if guildID == 0 || userID == 0 || sessionID == "" || token == "" {
|
|
|
|
return ErrMissingForIdentify
|
|
|
|
}
|
|
|
|
|
2020-07-11 19:50:32 +00:00
|
|
|
return c.SendCtx(ctx, IdentifyOP, IdentifyData{
|
2020-04-20 01:21:10 +00:00
|
|
|
GuildID: guildID,
|
|
|
|
UserID: userID,
|
|
|
|
SessionID: sessionID,
|
|
|
|
Token: token,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// OPCode 1
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-example-select-protocol-payload
|
2020-04-20 01:21:10 +00:00
|
|
|
type SelectProtocol struct {
|
|
|
|
Protocol string `json:"protocol"`
|
|
|
|
Data SelectProtocolData `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type SelectProtocolData struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
Port uint16 `json:"port"`
|
|
|
|
Mode string `json:"mode"`
|
|
|
|
}
|
|
|
|
|
2020-04-25 02:36:33 +00:00
|
|
|
// SelectProtocol sends a Select Protocol operation (opcode 1) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) SelectProtocol(data SelectProtocol) error {
|
2020-07-11 19:50:32 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return c.SelectProtocolCtx(ctx, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectProtocolCtx sends a Select Protocol operation (opcode 1) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) SelectProtocolCtx(ctx context.Context, data SelectProtocol) error {
|
|
|
|
return c.SendCtx(ctx, SelectProtocolOP, data)
|
2020-04-20 01:21:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// OPCode 3
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#heartbeating-example-heartbeat-payload
|
2020-07-11 19:50:32 +00:00
|
|
|
// type Heartbeat uint64
|
2020-04-20 01:21:10 +00:00
|
|
|
|
2020-04-25 02:36:33 +00:00
|
|
|
// Heartbeat sends a Heartbeat operation (opcode 3) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) Heartbeat() error {
|
2020-07-11 19:50:32 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return c.HeartbeatCtx(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HeartbeatCtx sends a Heartbeat operation (opcode 3) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) HeartbeatCtx(ctx context.Context) error {
|
|
|
|
return c.SendCtx(ctx, HeartbeatOP, time.Now().UnixNano())
|
2020-04-20 01:21:10 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#speaking
|
2020-04-25 02:36:33 +00:00
|
|
|
type SpeakingFlag uint64
|
2020-04-20 01:21:10 +00:00
|
|
|
|
|
|
|
const (
|
2020-04-25 02:36:33 +00:00
|
|
|
Microphone SpeakingFlag = 1 << iota
|
2020-04-20 01:21:10 +00:00
|
|
|
Soundshare
|
|
|
|
Priority
|
|
|
|
)
|
|
|
|
|
|
|
|
// OPCode 5
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#speaking-example-speaking-payload
|
2020-04-20 01:21:10 +00:00
|
|
|
type SpeakingData struct {
|
2020-04-25 02:36:33 +00:00
|
|
|
Speaking SpeakingFlag `json:"speaking"`
|
|
|
|
Delay int `json:"delay"`
|
|
|
|
SSRC uint32 `json:"ssrc"`
|
2020-04-20 01:21:10 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 02:36:33 +00:00
|
|
|
// Speaking sends a Speaking operation (opcode 5) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) Speaking(flag SpeakingFlag) error {
|
2020-07-11 19:50:32 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return c.SpeakingCtx(ctx, flag)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SpeakingCtx sends a Speaking operation (opcode 5) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) SpeakingCtx(ctx context.Context, flag SpeakingFlag) error {
|
2020-04-20 01:21:10 +00:00
|
|
|
// How do we allow a user to stop speaking?
|
|
|
|
// Also: https://discordapp.com/developers/docs/topics/voice-connections#voice-data-interpolation
|
|
|
|
|
2020-07-11 19:50:32 +00:00
|
|
|
return c.SendCtx(ctx, SpeakingOP, SpeakingData{
|
2020-04-25 02:36:33 +00:00
|
|
|
Speaking: flag,
|
2020-04-20 01:21:10 +00:00
|
|
|
Delay: 0,
|
|
|
|
SSRC: c.ready.SSRC,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// OPCode 7
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#resuming-voice-connection-example-resume-connection-payload
|
2020-04-20 01:21:10 +00:00
|
|
|
type ResumeData struct {
|
2020-07-21 20:27:59 +00:00
|
|
|
GuildID discord.GuildID `json:"server_id"` // yes, this should be "server_id"
|
|
|
|
SessionID string `json:"session_id"`
|
|
|
|
Token string `json:"token"`
|
2020-04-20 01:21:10 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 02:36:33 +00:00
|
|
|
// Resume sends a Resume operation (opcode 7) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) Resume() error {
|
2020-07-11 19:50:32 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
return c.ResumeCtx(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResumeCtx sends a Resume operation (opcode 7) to the Gateway Gateway.
|
|
|
|
func (c *Gateway) ResumeCtx(ctx context.Context) error {
|
2020-04-25 02:36:33 +00:00
|
|
|
guildID := c.state.GuildID
|
|
|
|
sessionID := c.state.SessionID
|
|
|
|
token := c.state.Token
|
2020-04-20 01:21:10 +00:00
|
|
|
|
2020-07-29 20:10:17 +00:00
|
|
|
if !guildID.IsValid() || sessionID == "" || token == "" {
|
2020-04-20 01:21:10 +00:00
|
|
|
return ErrMissingForResume
|
|
|
|
}
|
|
|
|
|
2020-07-11 19:50:32 +00:00
|
|
|
return c.SendCtx(ctx, ResumeOP, ResumeData{
|
2020-04-20 01:21:10 +00:00
|
|
|
GuildID: guildID,
|
|
|
|
SessionID: sessionID,
|
|
|
|
Token: token,
|
|
|
|
})
|
|
|
|
}
|