gavalink/lavalink.go

181 lines
3.4 KiB
Go
Raw Permalink Normal View History

2018-08-24 01:24:20 +00:00
package gavalink
import (
"context"
2018-08-24 01:24:20 +00:00
"errors"
"golang.org/x/sync/errgroup"
2018-08-24 01:24:20 +00:00
"log"
2019-10-13 04:42:08 +00:00
"net/http"
2018-08-24 01:24:20 +00:00
"os"
"runtime"
"sync"
2019-10-13 04:42:08 +00:00
"time"
2018-08-24 01:24:20 +00:00
)
// Log sets the log.Logger gavalink will write to
var Log *log.Logger
func init() {
Log = log.New(os.Stdout, "(gavalink) ", 0)
}
// Lavalink manages a connection to Lavalink Nodes
type Lavalink struct {
shards int
2018-08-24 01:24:20 +00:00
userID string
nodes []*Node
players map[string]*Player
playersMu sync.RWMutex
2019-10-13 03:57:59 +00:00
// Event handlers
handlersMu sync.RWMutex
handlers map[string][]*eventHandlerInstance
onceHandlers map[string][]*eventHandlerInstance
capabilities map[string]interface{}
2019-10-13 03:57:59 +00:00
BestNodeFunc func([]*Node) (*Node, error)
2018-08-24 01:24:20 +00:00
}
var (
errNoNodes = errors.New("No nodes present")
errNodeNotFound = errors.New("Couldn't find that node")
errPlayerNotFound = errors.New("Couldn't find a player for that guild")
errVolumeOutOfRange = errors.New("Volume is out of range, must be within [0, 1000]")
errInvalidVersion = errors.New("This library requires Lavalink >= 3")
errUnknownPayload = errors.New("Lavalink sent an unknown payload")
)
// NewLavalink creates a new Lavalink manager
func NewLavalink(shards int, userID string) *Lavalink {
2018-08-24 01:24:20 +00:00
return &Lavalink{
2019-10-13 03:57:59 +00:00
shards: shards,
userID: userID,
2018-08-24 01:24:20 +00:00
players: make(map[string]*Player),
2019-10-13 03:57:59 +00:00
BestNodeFunc: BestNodeByPenalties,
2018-08-24 01:24:20 +00:00
}
}
// AddNodes adds a node to the Lavalink manager
// This function calls all of the node connect methods at once.
// TODO perhaps add a pool/max at a time limit?
func (l *Lavalink) AddNodes(nodeConfigs ...NodeConfig) error {
2019-10-13 04:42:08 +00:00
client := &http.Client{
Timeout: 60 * time.Second,
}
eg, ctx := errgroup.WithContext(context.Background())
for _, c := range nodeConfigs {
eg.Go(func() error {
n := &Node{
config: c,
manager: l,
client: client,
}
err := n.open(ctx)
if err != nil {
return err
}
2019-10-13 01:13:23 +00:00
l.nodes = append(l.nodes, n)
2019-01-13 23:37:38 +00:00
return nil
})
}
return eg.Wait()
2018-08-24 01:24:20 +00:00
}
// RemoveNode removes a node from the manager
func (l *Lavalink) removeNode(node *Node) error {
2018-08-24 01:24:20 +00:00
idx := -1
for i, n := range l.nodes {
2019-10-13 03:57:59 +00:00
if n == node {
2018-08-24 01:24:20 +00:00
idx = i
break
}
}
if idx == -1 {
return errNodeNotFound
}
node.stop()
danglingPlayers := make([]*Player, 0)
l.playersMu.RLock()
for _, player := range l.players {
2019-10-13 03:57:59 +00:00
if player.node == node {
player.node = nil
n, err := l.BestNode()
2019-10-13 03:57:59 +00:00
if err != nil || n == nil {
danglingPlayers = append(danglingPlayers, player)
2019-10-13 03:57:59 +00:00
continue
}
player.ChangeNode(n)
}
}
l.playersMu.RUnlock()
2019-10-13 03:57:59 +00:00
if len(danglingPlayers) > 0 {
for _, player := range danglingPlayers {
player.Destroy()
}
}
2018-08-24 01:24:20 +00:00
// temp var for easier reading
n := l.nodes
2018-08-24 01:24:20 +00:00
z := len(n) - 1
n[idx] = n[z] // swap idx with last
n = n[:z]
l.nodes = n
2018-08-24 01:24:20 +00:00
return nil
}
// BestNode returns the Node with the lowest latency
func (l *Lavalink) BestNode() (*Node, error) {
if len(l.nodes) < 1 {
2018-08-24 01:24:20 +00:00
return nil, errNoNodes
}
return l.BestNodeFunc(l.nodes)
2018-08-24 01:24:20 +00:00
}
// GetPlayer gets a player for a guild
func (l *Lavalink) GetPlayer(guild string) (*Player, error) {
l.playersMu.RLock()
defer l.playersMu.RUnlock()
p, ok := l.players[guild]
2019-01-13 23:37:38 +00:00
2018-08-24 01:24:20 +00:00
if !ok {
return nil, errPlayerNotFound
}
2019-01-13 23:37:38 +00:00
2018-08-24 01:24:20 +00:00
return p, nil
}
// Add capabilities mappings to the client, letting the server know what we support
func (l *Lavalink) AddCapability(key string, i interface{}) {
if l.capabilities == nil {
l.capabilities = make(map[string]interface{})
}
l.capabilities[key] = i
}
func gavalinkUserAgent() string {
return "Gavalink (v1.0, " + runtime.Version() + ")"
}