181 lines
3.4 KiB
Go
181 lines
3.4 KiB
Go
package gavalink
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"golang.org/x/sync/errgroup"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// 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
|
|
userID string
|
|
|
|
nodes []*Node
|
|
|
|
players map[string]*Player
|
|
playersMu sync.RWMutex
|
|
|
|
// Event handlers
|
|
handlersMu sync.RWMutex
|
|
handlers map[string][]*eventHandlerInstance
|
|
onceHandlers map[string][]*eventHandlerInstance
|
|
|
|
capabilities map[string]interface{}
|
|
|
|
BestNodeFunc func([]*Node) (*Node, error)
|
|
}
|
|
|
|
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 {
|
|
return &Lavalink{
|
|
shards: shards,
|
|
userID: userID,
|
|
players: make(map[string]*Player),
|
|
|
|
BestNodeFunc: BestNodeByPenalties,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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
|
|
}
|
|
|
|
l.nodes = append(l.nodes, n)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
return eg.Wait()
|
|
}
|
|
|
|
// RemoveNode removes a node from the manager
|
|
func (l *Lavalink) removeNode(node *Node) error {
|
|
idx := -1
|
|
for i, n := range l.nodes {
|
|
if n == node {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx == -1 {
|
|
return errNodeNotFound
|
|
}
|
|
|
|
node.stop()
|
|
|
|
danglingPlayers := make([]*Player, 0)
|
|
|
|
l.playersMu.RLock()
|
|
for _, player := range l.players {
|
|
if player.node == node {
|
|
player.node = nil
|
|
|
|
n, err := l.BestNode()
|
|
|
|
if err != nil || n == nil {
|
|
danglingPlayers = append(danglingPlayers, player)
|
|
continue
|
|
}
|
|
|
|
player.ChangeNode(n)
|
|
}
|
|
}
|
|
l.playersMu.RUnlock()
|
|
|
|
if len(danglingPlayers) > 0 {
|
|
for _, player := range danglingPlayers {
|
|
player.Destroy()
|
|
}
|
|
}
|
|
|
|
// temp var for easier reading
|
|
n := l.nodes
|
|
z := len(n) - 1
|
|
|
|
n[idx] = n[z] // swap idx with last
|
|
n = n[:z]
|
|
|
|
l.nodes = n
|
|
return nil
|
|
}
|
|
|
|
// BestNode returns the Node with the lowest latency
|
|
func (l *Lavalink) BestNode() (*Node, error) {
|
|
if len(l.nodes) < 1 {
|
|
return nil, errNoNodes
|
|
}
|
|
|
|
return l.BestNodeFunc(l.nodes)
|
|
}
|
|
|
|
// 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]
|
|
|
|
if !ok {
|
|
return nil, errPlayerNotFound
|
|
}
|
|
|
|
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() + ")"
|
|
}
|