forked from lukasl-dev/waterlink
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
254 lines (217 loc) · 6.03 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package waterlink
import (
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/websocket"
"github.com/lukasl-dev/waterlink/equalizer"
"github.com/lukasl-dev/waterlink/play"
"io"
"net/http"
"net/url"
"strconv"
)
var (
ErrLoadFailed = errors.New("player loading failed: %w")
ErrNoMatches = errors.New("no matches found")
)
type Client struct {
wsHost string
httpHost string
conn *websocket.Conn
password string
userID string
totalShards int
}
// newPlay returns a new client with passed options.
func New(options ...Option) (*Client, error) {
c := &Client{
httpHost: "http://localhost:8080",
wsHost: "ws://localhost:8080",
}
// apply options
for _, option := range options {
if err := option(c); err != nil {
return nil, err
}
}
return c, nil
}
func (c *Client) authorize(header http.Header) {
header.Set("Authorization", c.password)
}
// Open opens the connection to the websocket server.
func (c *Client) Open() error {
header := http.Header{}
c.authorize(header)
header.Set("Num-Shards", strconv.FormatInt(int64(c.totalShards), 10))
header.Set("User-Id", c.userID)
var err error
c.conn, _, err = websocket.DefaultDialer.Dial(c.wsHost, header)
if err != nil {
return err
}
return nil
}
// send sends v as json to the websocket server.
func (c *Client) send(v interface{}) error {
return c.conn.WriteJSON(v)
}
// VoiceUpdate provides a voice server update.
func (c *Client) VoiceUpdate(guildID string, sessionID string, event VoiceServerUpdate) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
SessionID string `json:"sessionId"`
Event VoiceServerUpdate `json:"event"`
}
return c.send(body{
OP: opVoiceUpdate,
GuildID: guildID,
SessionID: sessionID,
Event: event,
})
}
// Play plays a specific track on a specific guild.
func (c *Client) Play(guildID string, trackID string, options ...play.Option) error {
p := play.New(guildID, trackID)
for _, option := range options {
if err := option(p); err != nil {
return err
}
}
return c.send(p)
}
// Play plays a specific track on a specific guild.
func (c *Client) PlayTrack(guildID string, track Track, options ...play.Option) error {
return c.Play(guildID, track.ID, options...)
}
// Stop stops the player.
func (c *Client) Stop(guildID string) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
}
return c.send(body{
OP: opStop,
GuildID: guildID,
})
}
// Pause pauses the playback.
func (c *Client) Pause(guildID string, pause bool) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
Pause bool `json:"pause"`
}
return c.send(body{
OP: opPause,
GuildID: guildID,
Pause: pause,
})
}
// Seek seeks a player.
func (c *Client) Seek(guildID string, position int) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
Position int `json:"position"`
}
return c.send(body{
OP: opSeek,
GuildID: guildID,
Position: position,
})
}
// Volume sets the player's volume.
func (c *Client) Volume(guildID string, volume int) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
Volume int `json:"volume"`
}
return c.send(body{
OP: opVolume,
GuildID: guildID,
Volume: volume,
})
}
// Equalizer uses the player's equalizer.
//
// There are 15 bands (0-14) that can be changed.
// Gain is the multiplier for the given band.
// The default value is 0. Valid values range from -0.25 to 1.0, where -0.25 means the given band is completely muted,
// and 0.25 means it is doubled.
// Modifying the gain could also change the volume of the output.
func (c *Client) Equalizer(guildID string, bands ...equalizer.Band) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
Bands []equalizer.Band `json:"bands"`
}
return c.send(body{
OP: opEqualizer,
GuildID: guildID,
Bands: bands,
})
}
// Destroy destroys a player.
//
// Tell the server to potentially disconnect from the voice server and potentially remove the player with all its data.
// This is useful if you want to move to a new node for a voice connection.
// Calling this op does not affect voice state, and you can send the same VOICE_SERVER_UPDATE to a new node.
func (c *Client) Destroy(guildID string) error {
type body struct {
OP op `json:"op"`
GuildID string `json:"guildId"`
}
return c.send(body{
OP: opDestroy,
GuildID: guildID,
})
}
func (c *Client) LoadTracks(identifier string) (LoadType, PlaylistInfo, []Track, error) {
type body struct {
LoadType LoadType `json:"loadType"`
PlaylistInfo PlaylistInfo `json:"playlistInfo"`
Tracks []Track `json:"tracks"`
Exception struct {
Message string `json:"message"`
Severity string `json:"severity"`
} `json:"exception"`
}
host, _ := url.Parse(c.httpHost)
host.Path = "/loadtracks"
query := host.Query()
query.Set("identifier", identifier)
host.RawQuery = query.Encode()
request, err := http.NewRequest("GET", host.String(), nil)
if err != nil {
return LoadTypeUnknown, PlaylistInfo{}, nil, err
}
c.authorize(request.Header)
response, err := http.DefaultClient.Do(request)
if err != nil {
return LoadTypeUnknown, PlaylistInfo{}, nil, err
}
//goland:noinspection GoUnhandledErrorResult
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
return LoadTypeUnknown, PlaylistInfo{}, nil, err
}
var marshal *body
if err := json.Unmarshal(responseBody, &marshal); err != nil {
return LoadTypeUnknown, PlaylistInfo{}, nil, err
}
switch marshal.LoadType {
case LoadTypeNoMatches:
return LoadTypeNoMatches, PlaylistInfo{}, nil, ErrNoMatches
case LoadTypeLoadFailed:
return LoadTypeLoadFailed, PlaylistInfo{}, nil, fmt.Errorf(
"%s: %s",
marshal.Exception.Severity, marshal.Exception.Message,
)
}
return marshal.LoadType, marshal.PlaylistInfo, marshal.Tracks, nil
}