forked from Clinet/clinet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
voice.go
660 lines (583 loc) · 22.8 KB
/
voice.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
package main
import (
"errors"
"fmt"
"math/rand"
"net/url"
"regexp"
"github.com/bwmarrin/discordgo"
"github.com/jonas747/dca"
"github.com/rylio/ytdl"
"google.golang.org/api/youtube/v3"
)
var encodeOptionsPresetHigh = &dca.EncodeOptions{
Volume: 256,
Channels: 2,
FrameRate: 48000,
FrameDuration: 20,
Bitrate: 96,
Application: "lowdelay",
CompressionLevel: 10,
PacketLoss: 0,
BufferedFrames: 100,
VBR: true,
RawOutput: true,
}
// GuildData holds data specific to a guild
type GuildData struct {
AudioQueue []AudioQueueEntry
AudioNowPlaying AudioQueueEntry
VoiceData VoiceData
Queries map[string]*Query
YouTubeResults map[string]*YouTubeResultNav
}
func (guild *GuildData) QueueAddData(author, imageURL, title, thumbnailURL, mediaURL, sourceType string, requester *discordgo.User) {
var queueData AudioQueueEntry
queueData.Author = author
queueData.ImageURL = imageURL
queueData.MediaURL = mediaURL
queueData.Requester = requester
queueData.ThumbnailURL = thumbnailURL
queueData.Title = title
queueData.Type = sourceType
guild.AudioQueue = append(guild.AudioQueue, queueData)
}
func (guild *GuildData) QueueAdd(audioQueueEntry AudioQueueEntry) {
guild.AudioQueue = append(guild.AudioQueue, audioQueueEntry)
}
func (guild *GuildData) QueueRemove(entry int) {
guild.AudioQueue = append(guild.AudioQueue[:entry], guild.AudioQueue[entry+1:]...)
}
func (guild *GuildData) QueueRemoveRange(start int, end int) {
for entry := end; entry < start; entry-- {
guild.AudioQueue = append(guild.AudioQueue[:entry], guild.AudioQueue[entry+1:]...)
}
}
func (guild *GuildData) QueueClear() {
guild.AudioQueue = nil
}
func (guild *GuildData) QueueGet(guildID string, entry int) AudioQueueEntry {
if len(guildData[guildID].AudioQueue) >= entry {
return guildData[guildID].AudioQueue[entry]
} else {
return AudioQueueEntry{}
}
}
func (guild *GuildData) QueueGetNext(guildID string) AudioQueueEntry {
if len(guildData[guildID].AudioQueue) > 0 {
return guildData[guildID].AudioQueue[0]
} else {
return AudioQueueEntry{}
}
}
//YouTube search results, interacted with via commands
type YouTubeResultNav struct {
//Used by struct functions
Query string //The search query used to retrieve the current results
TotalResults int64 //The total amount of results for the current search query
Results []*youtube.SearchResult //The results of the current page
PrevPageToken string //The token of the previous page of results
NextPageToken string //The token of the next page of results
PageNumber int //The numerical identifier of the current page
//Used by external functions for easy page management
ResponseID string //The message response ID used to display and update result listings
MaxResults int64 //The total amount of results per page
}
func (page *YouTubeResultNav) Prev() error {
if page.PageNumber == 0 {
return errors.New("No search pages found")
}
if page.PrevPageToken == "" {
return errors.New("No pages exist before current page")
}
searchCall := botData.BotClients.YouTube.Search.
List("id").
Q(page.Query).
PageToken(page.PrevPageToken)
response, err := searchCall.Do()
if err != nil {
return errors.New("Could not find any video results for the previous page")
}
page.PageNumber -= 1
page.Results = response.Items
page.PrevPageToken = response.PrevPageToken
page.NextPageToken = response.NextPageToken
return nil
}
func (page *YouTubeResultNav) Next() error {
if page.PageNumber == 0 {
return errors.New("No search pages found")
}
if page.NextPageToken == "" {
return errors.New("No pages exist after current page")
}
searchCall := botData.BotClients.YouTube.Search.
List("id").
Q(page.Query).
PageToken(page.NextPageToken)
response, err := searchCall.Do()
if err != nil {
return errors.New("Could not find any video results for the next page")
}
page.PageNumber += 1
page.Results = response.Items
page.PrevPageToken = response.PrevPageToken
page.NextPageToken = response.NextPageToken
return nil
}
func (page *YouTubeResultNav) GetResults() ([]*youtube.SearchResult, error) {
if len(page.Results) == 0 {
return nil, errors.New("No search results found")
}
return page.Results, nil
}
func (page *YouTubeResultNav) Search(query string) error {
if page.MaxResults == 0 {
page.MaxResults = int64(botData.BotOptions.YouTubeMaxResults)
}
page.Query = ""
page.PageNumber = 0
page.TotalResults = 0
page.Results = nil
page.PrevPageToken = ""
page.NextPageToken = ""
searchCall := botData.BotClients.YouTube.Search.
List("id").
Q(query).
MaxResults(page.MaxResults).
Type("video")
response, err := searchCall.Do()
if err != nil {
return errors.New("Could not find any video results for the specified query")
}
page.Query = query
page.PageNumber = 1
page.TotalResults = response.PageInfo.TotalResults
page.Results = response.Items
page.PrevPageToken = response.PrevPageToken
page.NextPageToken = response.NextPageToken
return nil
}
type AudioQueueEntry struct {
Author string
ImageURL string
MediaURL string
Requester *discordgo.User
RequestMessageID string //Used for if someone edits their request
ThumbnailURL string
Title string
Type string
}
func (audioQueueEntry *AudioQueueEntry) GetNowPlayingEmbed() *discordgo.MessageEmbed {
switch audioQueueEntry.Type {
case "youtube":
return NewEmbed().
AddField("Now Playing from YouTube", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF0000).MessageEmbed
case "soundcloud":
return NewEmbed().
AddField("Now Playing from SoundCloud", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF7700).MessageEmbed
default:
return NewEmbed().
SetTitle("Now Playing").
AddField("URL", audioQueueEntry.MediaURL).
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetColor(0x1C1C1C).MessageEmbed
}
}
func (audioQueueEntry *AudioQueueEntry) GetNowPlayingDurationEmbed(stream *dca.StreamingSession) *discordgo.MessageEmbed {
//Get the current duration
currentDuration := secondsToHuman(stream.PlaybackPosition().Seconds())
switch audioQueueEntry.Type {
case "youtube":
return NewEmbed().
AddField("Now Playing from YouTube", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
AddField("Elapsed Time", currentDuration).
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF0000).MessageEmbed
case "soundcloud":
return NewEmbed().
AddField("Now Playing from SoundCloud", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
AddField("Elapsed Time", currentDuration).
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF7700).MessageEmbed
default:
return NewEmbed().
SetTitle("Now Playing").
AddField("URL", audioQueueEntry.MediaURL).
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
AddField("Elapsed Time", currentDuration).
SetColor(0x1C1C1C).MessageEmbed
}
}
func (audioQueueEntry *AudioQueueEntry) GetQueueAddedEmbed() *discordgo.MessageEmbed {
if audioQueueEntry.Type == "" {
if isYouTubeURL(audioQueueEntry.MediaURL) {
audioQueueEntry.Type = "youtube"
} else if isSoundCloudURL(audioQueueEntry.MediaURL) {
audioQueueEntry.Type = "soundcloud"
} else {
audioQueueEntry.Type = "direct"
}
}
switch audioQueueEntry.Type {
case "youtube":
return NewEmbed().
AddField("Added to Queue from YouTube", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF0000).MessageEmbed
case "soundcloud":
return NewEmbed().
AddField("Added to Queue from SoundCloud", "["+audioQueueEntry.Title+"]("+audioQueueEntry.MediaURL+") by **"+audioQueueEntry.Author+"**").
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetThumbnail(audioQueueEntry.ThumbnailURL).
SetColor(0xFF7700).MessageEmbed
default:
return NewEmbed().
SetTitle("Added to Queue").
AddField("URL", audioQueueEntry.MediaURL).
AddField("Requester", "<@"+audioQueueEntry.Requester.ID+">").
SetColor(0x1C1C1C).MessageEmbed
}
}
func (audioQueueEntry *AudioQueueEntry) FillMetadata() {
if audioQueueEntry.Type == "" {
if isYouTubeURL(audioQueueEntry.MediaURL) {
audioQueueEntry.Type = "youtube"
} else if isSoundCloudURL(audioQueueEntry.MediaURL) {
audioQueueEntry.Type = "soundcloud"
} else {
audioQueueEntry.Type = "direct"
}
}
switch audioQueueEntry.Type {
case "youtube":
videoInfo, err := ytdl.GetVideoInfo(audioQueueEntry.MediaURL)
if err != nil {
return
}
audioQueueEntry.Author = videoInfo.Author
audioQueueEntry.ImageURL = videoInfo.GetThumbnailURL("maxresdefault").String()
audioQueueEntry.ThumbnailURL = videoInfo.GetThumbnailURL("default").String()
audioQueueEntry.Title = videoInfo.Title
case "soundcloud":
audioInfo, err := botData.BotClients.SoundCloud.GetTrackInfo(audioQueueEntry.MediaURL)
if err != nil {
return
}
audioQueueEntry.Author = audioInfo.Artist
audioQueueEntry.ImageURL = audioInfo.ArtURL
audioQueueEntry.ThumbnailURL = audioInfo.ArtURL
audioQueueEntry.Title = audioInfo.Title
}
}
// VoiceData contains data about the current voice session
type VoiceData struct {
VoiceConnection *discordgo.VoiceConnection `json:"-"` //Holds data regarding the current voice connection
EncodingSession *dca.EncodeSession `json:"-"` //Holds data regarding the current encoding session
EncodingOptions *dca.EncodeOptions //Holds data regarding the current encoding options
StreamingSession *dca.StreamingSession `json:"-"` //Holds data regarding the current streaming session
ChannelIDJoinedFrom string //The text channel that was used to bring the bot into the voice channel
IsPlaybackPreparing bool `json:"-"` //Whether or not the playback is being prepared
IsPlaybackRunning bool `json:"-"` //Whether or not playback is currently running
WasStoppedManually bool `json:"-"` //Whether or not playback was stopped manually or automatically
WasSkipped bool `json:"-"` //Whether or not playback was skipped
//Configuration settings that can be set via commands
RepeatLevel int //0 = No Repeat, 1 = Repeat Playlist, 2 = Repeat Now Playing
Shuffle bool
}
func voiceJoin(session *discordgo.Session, guildID, channelID, channelIDJoinedFrom string) error {
_, guildFound := guildData[guildID]
if guildFound {
if guildData[guildID].VoiceData.VoiceConnection != nil {
if guildData[guildID].VoiceData.VoiceConnection.ChannelID == channelID {
debugLog("> Found previous matching voice connection, staying...", false)
return nil //We're already in the selected voice channel
} else {
debugLog("> Found previous mismatch voice connection, leaving...", false)
err := voiceLeave(guildID, channelID)
if err != nil {
return errors.New("Error leaving specified voice channel")
}
}
}
} else {
debugLog("> Guild data not found, initializing...", false)
guildData[guildID] = &GuildData{}
guildData[guildID].VoiceData = VoiceData{}
}
voiceConnection, err := session.ChannelVoiceJoin(guildID, channelID, false, false)
if err != nil {
return errors.New("Error joining specified voice channel.")
} else {
guildData[guildID].VoiceData.VoiceConnection = voiceConnection
guildData[guildID].VoiceData.ChannelIDJoinedFrom = channelIDJoinedFrom
return nil
}
}
func voiceLeave(guildID, channelID string) error {
_, guildFound := guildData[guildID]
if guildFound {
if guildData[guildID].VoiceData.VoiceConnection != nil {
debugLog("> Found previous voice connection, leaving...", false)
guildData[guildID].VoiceData.VoiceConnection.Disconnect()
guildData[guildID].VoiceData = VoiceData{}
return nil
} else {
return errors.New("Not connected to specified voice channel.")
}
} else {
return errors.New("Not connected to specified voice channel.")
}
}
func voicePlay(guildID, mediaURL string) error {
if guildData[guildID].VoiceData.VoiceConnection == nil {
return errors.New("Not connected to a voice channel.")
}
_, err := url.ParseRequestURI(mediaURL)
if err != nil {
return errors.New("Specified URL is invalid.")
}
mediaURL, err = getMediaURL(mediaURL)
if err != nil {
return err
}
//Setup pointers to guild data for local usage
//var voiceConnection *discordgo.VoiceConnection = guildData[guildID].VoiceData.VoiceConnection
//var encodingSession *dca.EncodeSession = guildData[guildID].VoiceData.EncodingSession
//var streamingSession *dca.StreamingSession = guildData[guildID].VoiceData.StreamingSession
//Setup the audio encoding options
if guildData[guildID].VoiceData.EncodingOptions == nil {
guildData[guildID].VoiceData.EncodingOptions = encodeOptionsPresetHigh
}
guildData[guildID].VoiceData.EncodingOptions.RawOutput = true
guildData[guildID].VoiceData.EncodingOptions.Bitrate = 96
guildData[guildID].VoiceData.EncodingOptions.Application = "lowdelay"
//Create the encoding session to encode the audio to DCA in a stream
guildData[guildID].VoiceData.EncodingSession, err = dca.EncodeFile(mediaURL, guildData[guildID].VoiceData.EncodingOptions)
if err != nil {
debugLog("[Voice] Error encoding file ["+mediaURL+"]: "+fmt.Sprintf("%v", err), false)
return errors.New("Error encoding specified URL to DCA audio.")
}
//Set speaking to true
guildData[guildID].VoiceData.VoiceConnection.Speaking(true)
//Make a channel for signals when playback is finished
done := make(chan error)
//Create the audio stream
//streamingSession = dca.NewStream(encodingSession, voiceConnection, done)
guildData[guildID].VoiceData.StreamingSession = dca.NewStream(guildData[guildID].VoiceData.EncodingSession, guildData[guildID].VoiceData.VoiceConnection, done)
//Set playback running bool to true
guildData[guildID].VoiceData.IsPlaybackRunning = true
//Set playback stopped manually bool to false
guildData[guildID].VoiceData.WasStoppedManually = false
for guildData[guildID].VoiceData.IsPlaybackRunning {
select {
case err := <-done:
if err != nil {
guildData[guildID].VoiceData.IsPlaybackRunning = false
}
}
}
//Set speaking to false
guildData[guildID].VoiceData.VoiceConnection.Speaking(false)
//Check streaming session for why playback stopped
_, err = guildData[guildID].VoiceData.StreamingSession.Finished()
//Clean up streaming session
guildData[guildID].VoiceData.StreamingSession = nil
//Clean up encoding session
guildData[guildID].VoiceData.EncodingSession.Stop()
guildData[guildID].VoiceData.EncodingSession.Cleanup()
guildData[guildID].VoiceData.EncodingSession = nil
//If playback stopped from an error, return that error
if err != nil {
debugLog("-- Playback error", false)
return err
}
return nil
}
func voicePlayWrapper(session *discordgo.Session, guildID, channelID, mediaURL string) {
err := voicePlay(guildID, mediaURL)
if guildData[guildID].VoiceData.RepeatLevel == 2 { //Repeat Now Playing
for guildData[guildID].VoiceData.RepeatLevel == 2 {
err = voicePlay(guildID, mediaURL)
if err != nil {
guildData[guildID].AudioNowPlaying = AudioQueueEntry{} //Clear now playing slot
errorEmbed := NewErrorEmbed("Voice Error", "There was an error playing the specified audio.")
session.ChannelMessageSendEmbed(channelID, errorEmbed)
return
}
}
}
if guildData[guildID].VoiceData.RepeatLevel == 1 { //Repeat Playlist
guildData[guildID].QueueAdd(guildData[guildID].AudioNowPlaying) //Shift the now playing entry to the end of the guild queue
}
guildData[guildID].AudioNowPlaying = AudioQueueEntry{} //Clear now playing slot
if err != nil {
errorEmbed := NewErrorEmbed("Voice Error", "There was an error playing the specified audio.")
session.ChannelMessageSendEmbed(channelID, errorEmbed)
return
} else {
if guildData[guildID].VoiceData.WasStoppedManually {
guildData[guildID].VoiceData.WasStoppedManually = false
} else if guildData[guildID].VoiceData.IsPlaybackRunning == false || guildData[guildID].VoiceData.WasSkipped == true {
guildData[guildID].VoiceData.WasSkipped = false //Reset skip bool in case it was true
//When the song finishes playing, we should run on a loop to make sure the next songs continue playing
for len(guildData[guildID].AudioQueue) > 0 {
//Move next guild queue entry into now playing slot
if guildData[guildID].VoiceData.Shuffle {
//Pseudo-shuffle™, replace with legitimate shuffle method later so user can return to non-shuffled playlist
randomEntry := rand.Intn(len(guildData[guildID].AudioQueue))
guildData[guildID].AudioNowPlaying = guildData[guildID].AudioQueue[randomEntry]
guildData[guildID].QueueRemove(randomEntry)
} else {
guildData[guildID].AudioNowPlaying = guildData[guildID].AudioQueue[0]
guildData[guildID].QueueRemove(0)
}
//Create and display now playing embed
nowPlayingEmbed := guildData[guildID].AudioNowPlaying.GetNowPlayingEmbed()
session.ChannelMessageSendEmbed(channelID, nowPlayingEmbed)
//Play audio
err := voicePlay(guildID, guildData[guildID].AudioNowPlaying.MediaURL)
if guildData[guildID].VoiceData.RepeatLevel == 2 { //Repeat Now Playing
for guildData[guildID].VoiceData.RepeatLevel == 2 {
err = voicePlay(guildID, guildData[guildID].AudioNowPlaying.MediaURL)
if err != nil {
guildData[guildID].AudioNowPlaying = AudioQueueEntry{} //Clear now playing slot
errorEmbed := NewErrorEmbed("Voice Error", "There was an error playing the specified audio.")
session.ChannelMessageSendEmbed(channelID, errorEmbed)
return
}
}
}
if guildData[guildID].VoiceData.RepeatLevel == 1 { //Repeat Playlist
guildData[guildID].QueueAdd(guildData[guildID].AudioNowPlaying) //Shift the now playing entry to the end of the guild queue
}
guildData[guildID].AudioNowPlaying = AudioQueueEntry{} //Clear now playing slot
if err != nil {
errorEmbed := NewErrorEmbed("Voice Error", "There was an error playing the specified audio.")
session.ChannelMessageSendEmbed(channelID, errorEmbed)
return //Prevent next guild queue entry from playing
} else {
if guildData[guildID].VoiceData.WasStoppedManually {
guildData[guildID].VoiceData.WasStoppedManually = false
return //Prevent next guild queue entry from playing
}
}
}
voiceLeave(guildID, channelID) //We're done with everything so leave the voice channel
leaveEmbed := NewGenericEmbed("Voice", "Finished playing the queue.")
session.ChannelMessageSendEmbed(channelID, leaveEmbed)
}
}
}
func voiceStop(guildID string) {
if guildData[guildID] != nil {
guildData[guildID].VoiceData.WasStoppedManually = true //Make sure other threads know it was stopped manually
guildData[guildID].VoiceData.EncodingSession.Stop() //Stop the encoding session manually
guildData[guildID].VoiceData.EncodingSession.Cleanup() //Cleanup the encoding session
guildData[guildID].VoiceData.IsPlaybackRunning = false //Let the voice play function clean up on its own
}
}
func voiceSkip(guildID string) {
if guildData[guildID] != nil {
guildData[guildID].VoiceData.WasSkipped = true //Let the voice play wrapper function continue to the next song if available
guildData[guildID].VoiceData.EncodingSession.Stop() //Stop the encoding session manually
guildData[guildID].VoiceData.EncodingSession.Cleanup() //Cleanup the encoding session
guildData[guildID].VoiceData.IsPlaybackRunning = false //Let the voice play function clean up on its own
}
}
func voiceIsStreaming(guildID string) bool {
if guildData[guildID] == nil {
return false
}
return guildData[guildID].VoiceData.IsPlaybackRunning
}
func voiceGetPauseState(guildID string) (bool, error) {
if guildData[guildID].VoiceData.StreamingSession == nil {
return false, errors.New("Could not find the streaming session for the specified guild.")
}
isPaused := guildData[guildID].VoiceData.StreamingSession.Paused()
return isPaused, nil
}
func voicePause(guildID string) (bool, error) {
if guildData[guildID].VoiceData.StreamingSession == nil {
return false, errors.New("Could not find the streaming session for the specified guild.")
}
isPaused := guildData[guildID].VoiceData.StreamingSession.Paused()
if isPaused {
return true, errors.New("The specified guild's streaming session is already paused.")
}
guildData[guildID].VoiceData.StreamingSession.SetPaused(true)
return true, nil
}
func voiceResume(guildID string) (bool, error) {
if guildData[guildID].VoiceData.StreamingSession == nil {
return false, errors.New("Could not find the streaming session for the specified guild.")
}
isPaused := guildData[guildID].VoiceData.StreamingSession.Paused()
if isPaused {
guildData[guildID].VoiceData.StreamingSession.SetPaused(false)
return true, nil
}
return true, errors.New("The specified guild's streaming session is already playing.")
}
func voiceGetQuery(query string) (string, error) {
call := botData.BotClients.YouTube.Search.List("id").
Q(query).
MaxResults(50)
response, err := call.Do()
if err != nil {
return "", errors.New("Could not find any results for the specified query.")
}
for _, item := range response.Items {
if item.Id.Kind == "youtube#video" {
url := "https://youtube.com/watch?v=" + item.Id.VideoId
return url, nil
}
}
return "", errors.New("Could not find a video result for the specified query.")
}
func getMediaURL(url string) (string, error) {
if isYouTubeURL(url) {
videoInfo, err := ytdl.GetVideoInfo(url)
if err != nil {
return url, err
}
format := videoInfo.Formats.Extremes(ytdl.FormatAudioBitrateKey, true)[0]
mediaURL, err := videoInfo.GetDownloadURL(format)
if err != nil {
return url, err
}
return mediaURL.String(), nil
}
if isSoundCloudURL(url) {
audioInfo, err := botData.BotClients.SoundCloud.GetTrackInfo(url)
if err != nil {
return url, err
}
return audioInfo.DownloadURL, nil
}
return url, nil
}
func isYouTubeURL(url string) bool {
regexpHasYouTube, _ := regexp.MatchString("(?:https?:\\/\\/)?(?:www\\.)?youtu\\.?be(?:\\.com)?\\/?.*(?:watch|embed)?(?:.*v=|v\\/|\\/)(?:[\\w-_]+)", url)
if regexpHasYouTube {
return true
}
return false
}
func isSoundCloudURL(url string) bool {
regexpHasSoundCloud, _ := regexp.MatchString("^(https?:\\/\\/)?(www.)?(m\\.)?soundcloud\\.com\\/[\\w\\-\\.]+(\\/)+[\\w\\-\\.]+/?$", url)
if regexpHasSoundCloud {
return true
}
return false
}