Initial commit
commit
be2614c7fb
|
@ -0,0 +1,12 @@
|
||||||
|
*.sh
|
||||||
|
dndmusicbot
|
||||||
|
youtube-dl
|
||||||
|
oauth2-proxy
|
||||||
|
test/*
|
||||||
|
cookies.txt
|
||||||
|
config
|
||||||
|
css/*.min.css
|
||||||
|
js/*.min.js
|
||||||
|
|
||||||
|
bin
|
||||||
|
ambiance/*.mp3
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fnNoExt(fileName string) string {
|
||||||
|
return fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAmbiance() ([]string, error) {
|
||||||
|
files, err := os.ReadDir("./ambiance")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for _, file := range files {
|
||||||
|
out = append(out, fnNoExt(file.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/dpup/gohubbub"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
"github.com/r3labs/sse/v2"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/api/youtube/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
channels int = 2 // 1 for mono, 2 for stereo
|
||||||
|
sampleRate int = 48000 // audio sampling rate
|
||||||
|
frameSize int = 960 // uint16 size of each audio frame
|
||||||
|
maxBytes int = (frameSize * 2) * 2 // max size of opus data
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app = new(App)
|
||||||
|
config = viper.GetViper()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("bot.go loading..")
|
||||||
|
config.SetConfigName("config")
|
||||||
|
config.SetConfigType("yaml")
|
||||||
|
config.AddConfigPath(".")
|
||||||
|
err := config.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mux = http.NewServeMux()
|
||||||
|
|
||||||
|
go http.ListenAndServe(":8826", app.mux)
|
||||||
|
|
||||||
|
log.Println("bot.go done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
discord *discordgo.Session
|
||||||
|
voice *discordgo.VoiceConnection
|
||||||
|
youtube *youtube.Service
|
||||||
|
queue *Queue
|
||||||
|
ambiance *Queue
|
||||||
|
curamb string
|
||||||
|
events events.EventEmmiter
|
||||||
|
next bool
|
||||||
|
db *pgx.Conn
|
||||||
|
sse *sse.Server
|
||||||
|
router *httprouter.Router
|
||||||
|
active []string
|
||||||
|
plidx int
|
||||||
|
playlist *Playlist
|
||||||
|
plm *sync.RWMutex
|
||||||
|
hubbub *gohubbub.Client
|
||||||
|
mux *http.ServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app.plm = &sync.RWMutex{}
|
||||||
|
ticker := time.NewTicker(300 * time.Millisecond)
|
||||||
|
|
||||||
|
sc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sc:
|
||||||
|
app.db.Close(context.Background())
|
||||||
|
app.queue.Reset()
|
||||||
|
app.voice.Close()
|
||||||
|
app.discord.Close()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
app.events.Emit("tick")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing {
|
||||||
|
background-color: burlywood;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2.bot {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background-color: #80cbc4;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #073642
|
||||||
|
}
|
||||||
|
|
||||||
|
.container2 {
|
||||||
|
display: block;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
/* background-color: #fefefe;*/
|
||||||
|
margin: 15% auto; /* 15% from the top and centered */
|
||||||
|
padding: 20px;
|
||||||
|
/*border: 1px solid #888;*/
|
||||||
|
width: 80%; /* Could be more or less, depending on screen size */
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Modal (background) */
|
||||||
|
.modal {
|
||||||
|
display: block; /* Hidden by default */
|
||||||
|
position: fixed; /* Stay in place */
|
||||||
|
z-index: 1; /* Sit on top */
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%; /* Full width */
|
||||||
|
height: 100%; /* Full height */
|
||||||
|
overflow: auto; /* Enable scroll if needed */
|
||||||
|
background-color: rgb(0,0,0); /* Fallback color */
|
||||||
|
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Content/Box */
|
||||||
|
.modal-content {
|
||||||
|
margin: 15% auto; /* 15% from the top and centered */
|
||||||
|
padding: 20px;
|
||||||
|
width: 80%; /* Could be more or less, depending on screen size */
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > div.container2 > input[type=text]:nth-child(2) {
|
||||||
|
width: 512px;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
connstring = "host=localhost user=steino dbname=dndmusicbot sslmode=disable"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("db.go loading..")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app.db, err = pgx.Connect(context.Background(), config.GetString("db.connstring"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("db.go done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
Id uuid.UUID
|
||||||
|
Url string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app App) GetPlaylists() (playlists []Playlist, err error) {
|
||||||
|
log.Println(app.db.Ping(context.Background()))
|
||||||
|
rows, err := app.db.Query(context.Background(), "SELECT id, url, title FROM playlists")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
playlists, err = pgx.CollectRows(rows, pgx.RowToStructByName[Playlist])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app App) GetPlaylist(id uuid.UUID) (*Playlist, error) {
|
||||||
|
rows, err := app.db.Query(context.Background(), "SELECT id, url, title FROM playlists where id=$1 limit 1", id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//var out Playlist
|
||||||
|
out, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Playlist])
|
||||||
|
if err != nil {
|
||||||
|
return new(Playlist), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app App) AddPlaylist(title string, uri string) (uuid.UUID, error) {
|
||||||
|
id := uuid.New()
|
||||||
|
_, err := app.db.Exec(context.Background(), "INSERT INTO playlists VALUES ($1, $2, $3)", id, title, uri)
|
||||||
|
if err != nil {
|
||||||
|
return *new(uuid.UUID), err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
discordspeaker "dndmusicbot/speaker"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("discord.go loading..")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
token := config.GetString("discord.token")
|
||||||
|
guild := config.GetString("discord.guild")
|
||||||
|
channel := config.GetString("discord.channel")
|
||||||
|
|
||||||
|
app.discord, err = discordgo.New("Bot " + token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// app.discord.LogLevel = discordgo.LogDebug
|
||||||
|
|
||||||
|
err = app.discord.Open()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.voice, err = app.discord.ChannelVoiceJoin(guild, channel, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// app.voice.LogLevel = discordgo.LogDebug
|
||||||
|
|
||||||
|
discordspeaker.Init(app.voice)
|
||||||
|
log.Println("discord.go done.")
|
||||||
|
}
|
|
@ -0,0 +1,272 @@
|
||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GATEWAYBOTURL = "https://discord.com/api/gateway/bot"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PermissionVoiceConnect = 0x0000000000100000
|
||||||
|
PermissionVoiceSpeak = 0x0000000000200000
|
||||||
|
PermissionSendMessages = 0x0000000000000800
|
||||||
|
)
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
events events.EventEmmiter
|
||||||
|
httpClient *http.Client
|
||||||
|
dialer *websocket.Dialer
|
||||||
|
conn *websocket.Conn
|
||||||
|
token string
|
||||||
|
seq int
|
||||||
|
ready chan bool
|
||||||
|
vstateUpdate chan bool
|
||||||
|
vserverUpdate chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoiceConnection struct {
|
||||||
|
d *Connection
|
||||||
|
conn *websocket.Conn
|
||||||
|
sid string
|
||||||
|
e string
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoiceStateUpdate struct {
|
||||||
|
Op int `json:"op"`
|
||||||
|
Data struct {
|
||||||
|
Guild string `json:"guild_id"`
|
||||||
|
Channel string `json:"channel_id"`
|
||||||
|
Mute bool `json:"self_mute"`
|
||||||
|
Deaf bool `json:"self_dead"`
|
||||||
|
} `json:"d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Identify struct {
|
||||||
|
Op int `json:"op"`
|
||||||
|
|
||||||
|
Data struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Intents int `json:"intents"`
|
||||||
|
|
||||||
|
Properties struct {
|
||||||
|
OS string `json:"os"`
|
||||||
|
Browser string `json:"browser"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
} `json:"properties"`
|
||||||
|
} `json:"d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnection(token string) (*Connection, error) {
|
||||||
|
conn := new(Connection)
|
||||||
|
|
||||||
|
conn.token = token
|
||||||
|
|
||||||
|
conn.ready = make(chan bool)
|
||||||
|
conn.vserverUpdate = make(chan bool)
|
||||||
|
conn.vstateUpdate = make(chan bool)
|
||||||
|
|
||||||
|
conn.events = events.New()
|
||||||
|
conn.events.On("READY", conn.eventready)
|
||||||
|
|
||||||
|
conn.dialer = &websocket.Dialer{
|
||||||
|
HandshakeTimeout: 45 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.httpClient = &http.Client{}
|
||||||
|
gw, err := conn.GetGateway()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.conn, _, err = conn.dialer.Dial(gw, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, b, err := conn.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
go conn.handleGatewayEvent(b)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Identify
|
||||||
|
id := new(Identify)
|
||||||
|
id.Op = 2
|
||||||
|
id.Data.Token = token
|
||||||
|
id.Data.Intents = PermissionVoiceConnect | PermissionVoiceSpeak | PermissionSendMessages
|
||||||
|
id.Data.Properties.OS = "linux"
|
||||||
|
id.Data.Properties.Browser = "dndmusicbot/0.0.1"
|
||||||
|
id.Data.Properties.Device = "dndmusicbot"
|
||||||
|
|
||||||
|
json.NewEncoder(os.Stdout).Encode(id)
|
||||||
|
|
||||||
|
err = conn.conn.WriteJSON(id)
|
||||||
|
if err != nil {
|
||||||
|
conn.conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Wait for Ready!
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, err
|
||||||
|
case <-conn.ready:
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) NewVoiceConnection(guild string, channel string) (*VoiceConnection, error) {
|
||||||
|
vc := new(VoiceConnection)
|
||||||
|
vc.d = c
|
||||||
|
|
||||||
|
c.events.On("VOICE_STATE_UPDATE", vc.voiceStateUpdate)
|
||||||
|
c.events.On("VOICE_SERVER_UDPATE", vc.voiceServerUpdate)
|
||||||
|
|
||||||
|
msg := new(VoiceStateUpdate)
|
||||||
|
msg.Op = 4
|
||||||
|
msg.Data.Guild = guild
|
||||||
|
msg.Data.Channel = channel
|
||||||
|
msg.Data.Mute = false
|
||||||
|
msg.Data.Deaf = true
|
||||||
|
|
||||||
|
c.conn.WriteJSON(msg)
|
||||||
|
|
||||||
|
var state, server bool
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// We have to wait for both a vserverupdate and a vstateupdate before we continue.
|
||||||
|
for !state && !server {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-c.vserverUpdate:
|
||||||
|
server = true
|
||||||
|
case <-c.vstateUpdate:
|
||||||
|
state = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have gotten all the information by now.. continue..
|
||||||
|
|
||||||
|
return vc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *VoiceConnection) voiceStateUpdate(payload ...interface{}) {
|
||||||
|
vc.d.vstateUpdate <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *VoiceConnection) voiceServerUpdate(payload ...interface{}) {
|
||||||
|
vc.d.vserverUpdate <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ready event. Contains alot of information regarding our session.
|
||||||
|
func (c *Connection) eventready(payload ...interface{}) {
|
||||||
|
c.ready <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) handleGatewayEvent(js []byte) error {
|
||||||
|
eventdata := gjson.GetManyBytes(js, "op", "d", "s", "t")
|
||||||
|
|
||||||
|
op := eventdata[0]
|
||||||
|
data := eventdata[1]
|
||||||
|
seq := eventdata[2]
|
||||||
|
eventname := eventdata[3]
|
||||||
|
|
||||||
|
if seq.Exists() {
|
||||||
|
c.seq = int(seq.Int())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
c.conn.SetCloseHandler(func(code int, text string) error {
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
switch op.Int() {
|
||||||
|
case 0:
|
||||||
|
fmt.Printf("Event: %s Data: %+v\n", eventname.String(), data)
|
||||||
|
c.events.Emit(events.EventName(eventname.String()), data)
|
||||||
|
case 10: // Hello
|
||||||
|
hb_interval, err := time.ParseDuration(fmt.Sprintf("%dms", data.Get("heartbeat_interval").Int()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("We Received a Hello! Starting heartbeat on %.2fs interval\n", hb_interval.Seconds())
|
||||||
|
|
||||||
|
hb_ticker := time.NewTicker(hb_interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break
|
||||||
|
case <-hb_ticker.C:
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["op"] = 1
|
||||||
|
if c.seq == 0 {
|
||||||
|
msg["d"] = nil
|
||||||
|
} else {
|
||||||
|
msg["d"] = c.seq
|
||||||
|
}
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
log.Printf("Sending Heartbeat: %+v\n", msg)
|
||||||
|
err = c.conn.WriteJSON(msg)
|
||||||
|
if err != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 11:
|
||||||
|
log.Println("Heartbeat ACK!")
|
||||||
|
default:
|
||||||
|
log.Printf("%d: %+v\n", op.Int(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) GetGateway() (string, error) {
|
||||||
|
req, err := http.NewRequest("GET", GATEWAYBOTURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", "Bot "+c.token)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gjson.GetBytes(b, "url").String(), err
|
||||||
|
}
|
|
@ -0,0 +1,476 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"dndmusicbot/ffmpeg"
|
||||||
|
"dndmusicbot/loop"
|
||||||
|
discordspeaker "dndmusicbot/speaker"
|
||||||
|
"dndmusicbot/ytdl"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/faiface/beep/mp3"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SongInfo struct {
|
||||||
|
Playlist uuid.UUID `json:"playlist"`
|
||||||
|
PlaylistName string `json:"playlistname"`
|
||||||
|
Title string `json:"current"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
Length int `json:"len"`
|
||||||
|
Pause bool `json:"pause"`
|
||||||
|
Song string `json:"song"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var l = rate.Sometimes{Interval: 1 * time.Second}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("events.go loading...")
|
||||||
|
app.events = events.New()
|
||||||
|
|
||||||
|
app.events.On("load_playlist", app.loadPlaylist)
|
||||||
|
app.events.On("add_playlist", app.addPlaylist)
|
||||||
|
|
||||||
|
app.events.On("preload_song", app.preloadSong)
|
||||||
|
app.events.On("song_over", app.songOver)
|
||||||
|
//app.events.On("song_position", app.songPosition)
|
||||||
|
|
||||||
|
app.events.On("ambiance_play", app.ambiancePlay)
|
||||||
|
app.events.On("ambiance_stop", app.ambianceStop)
|
||||||
|
app.events.On("ambiance_add", app.ambianceAdd)
|
||||||
|
|
||||||
|
app.events.On("stop", app.stop)
|
||||||
|
app.events.On("next", app.nextSong)
|
||||||
|
app.events.On("prev", app.prevSong)
|
||||||
|
|
||||||
|
//app.events.On("tick", app.checkQueue)
|
||||||
|
app.events.On("tick", app.songPosition)
|
||||||
|
app.events.On("tick", app.checkTimeleft)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) songInfoEvent(event string) map[string]interface{} {
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["event"] = event
|
||||||
|
|
||||||
|
var title, channel string
|
||||||
|
|
||||||
|
switch current := app.queue.Current().(type) {
|
||||||
|
case *ffmpeg.PCM:
|
||||||
|
title = current.Player.Title
|
||||||
|
channel = current.Player.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
var plid uuid.UUID
|
||||||
|
var pltitle string
|
||||||
|
|
||||||
|
if app.playlist != nil {
|
||||||
|
plid = app.playlist.Id
|
||||||
|
pltitle = app.playlist.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
var song string
|
||||||
|
|
||||||
|
if app.active != nil && len(app.active) > 0 {
|
||||||
|
song = app.active[app.plidx]
|
||||||
|
}
|
||||||
|
|
||||||
|
msg["payload"] = SongInfo{
|
||||||
|
Playlist: plid,
|
||||||
|
PlaylistName: pltitle,
|
||||||
|
Title: title,
|
||||||
|
Channel: channel,
|
||||||
|
Position: app.queue.Position(),
|
||||||
|
Length: app.queue.Len(),
|
||||||
|
Pause: !app.queue.IsPlaying(),
|
||||||
|
Song: song,
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
|
if !(len(payload) > 0) {
|
||||||
|
log.Println("ambiance_play called without a payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fn string
|
||||||
|
switch data := payload[0].(type) {
|
||||||
|
case json.RawMessage:
|
||||||
|
var err error
|
||||||
|
err = json.Unmarshal(data, &fn)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Println("loadPlaylist called with invalid payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fmt.Sprintf("./ambiance/%s.mp3", fn))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
play, _, err := mp3.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
discordspeaker.Pause(false)
|
||||||
|
discordspeaker.Lock()
|
||||||
|
if app.ambiance.IsPlaying() {
|
||||||
|
app.ambiance.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := loop.Loop(-1, play)
|
||||||
|
app.ambiance.Add(loop)
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
|
||||||
|
app.curamb = fn
|
||||||
|
|
||||||
|
msg["event"] = "ambiance_play"
|
||||||
|
out["type"] = fn
|
||||||
|
msg["payload"] = out
|
||||||
|
ws_msg <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) ambianceStop(payload ...interface{}) {
|
||||||
|
log.Println("Stopping ambiance")
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.ambiance.Reset()
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_stop"
|
||||||
|
ws_msg <- msg
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) ambianceAdd(payload ...interface{}) {
|
||||||
|
if !(len(payload) > 0) {
|
||||||
|
log.Println("addPlaylist called without a payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("ambiance_add event received")
|
||||||
|
|
||||||
|
var data map[string]string
|
||||||
|
switch js := payload[0].(type) {
|
||||||
|
case json.RawMessage:
|
||||||
|
json.Unmarshal(js, &data)
|
||||||
|
default:
|
||||||
|
log.Println("newPlaylist called with invalid payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
amburl, ok := data["url"]
|
||||||
|
if !ok {
|
||||||
|
log.Println("addPlaylist without url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ambtitle, ok := data["title"]
|
||||||
|
if !ok {
|
||||||
|
log.Println("addPlaylist without title")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ytdl.DownloadAmbiance(amburl, ambtitle)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_add"
|
||||||
|
out["type"] = ambtitle
|
||||||
|
msg["payload"] = out
|
||||||
|
ws_msg <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) songPosition(payload ...interface{}) {
|
||||||
|
if !app.queue.IsPlaying() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Do(func() {
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
|
||||||
|
msg["event"] = "song_position"
|
||||||
|
if app.queue != nil {
|
||||||
|
out["len"] = app.queue.Len()
|
||||||
|
out["position"] = app.queue.Position()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg["payload"] = out
|
||||||
|
ws_msg <- msg
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) checkQueue(payload ...interface{}) {
|
||||||
|
if !app.queue.IsPlaying() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This needs some tweaking.
|
||||||
|
if app.queue.playing && !app.next && app.queue.QLen() == 0 {
|
||||||
|
|
||||||
|
log.Println("Queue is 0. It should never be 0..")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if app.queue.QLen() == 0 {
|
||||||
|
log.Println("Queue is still 0. Queueing a song.")
|
||||||
|
app.events.Emit("next")
|
||||||
|
}
|
||||||
|
log.Println("Seems queue is filled, doing nothing.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) checkTimeleft(payload ...interface{}) {
|
||||||
|
if !app.queue.IsPlaying() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeleft := app.queue.Len() - app.queue.Position()
|
||||||
|
if timeleft <= 10000 && timeleft > 0 && !app.next {
|
||||||
|
app.events.Emit("preload_song")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) songOver(payload ...interface{}) {
|
||||||
|
log.Println("song_over event received")
|
||||||
|
|
||||||
|
msg := app.songInfoEvent("song_info")
|
||||||
|
ws_msg <- msg
|
||||||
|
|
||||||
|
app.next = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) stop(payload ...interface{}) {
|
||||||
|
log.Println("stop event received")
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.queue.Reset()
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["event"] = "stop"
|
||||||
|
|
||||||
|
ws_msg <- msg
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) prevSong(payload ...interface{}) {
|
||||||
|
log.Println("prev_song event received")
|
||||||
|
song := app.GetPrevSong(app.active)
|
||||||
|
f, err := ffmpeg.NewPCM(song, sampleRate, channels)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to start new ffmpeg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.queue.Reset()
|
||||||
|
app.queue.Add(f)
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
msg := app.songInfoEvent("song_info")
|
||||||
|
ws_msg <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) nextSong(payload ...interface{}) {
|
||||||
|
log.Println("next_song event received")
|
||||||
|
song := app.GetNextSong(app.active)
|
||||||
|
f, err := ffmpeg.NewPCM(song, sampleRate, channels)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to start new ffmpeg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.queue.Reset()
|
||||||
|
app.queue.Add(f)
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
msg := app.songInfoEvent("song_info")
|
||||||
|
ws_msg <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) addPlaylist(payload ...interface{}) {
|
||||||
|
if !(len(payload) > 0) {
|
||||||
|
log.Println("addPlaylist called without a payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("add_playlist event received")
|
||||||
|
|
||||||
|
var data map[string]string
|
||||||
|
switch js := payload[0].(type) {
|
||||||
|
case json.RawMessage:
|
||||||
|
json.Unmarshal(js, &data)
|
||||||
|
default:
|
||||||
|
log.Println("newPlaylist called with invalid payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plurl, ok := data["url"]
|
||||||
|
if !ok {
|
||||||
|
log.Println("addPlaylist without url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pltitle, ok := data["title"]
|
||||||
|
if !ok {
|
||||||
|
log.Println("addPlaylist without title")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pl, err := url.Parse(plurl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("addPlaylist invalid url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plid := pl.Query().Get("list")
|
||||||
|
if plid == "" {
|
||||||
|
log.Println("addPlaylist missing list in url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = app.Playlist(plid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error getting youtube playlist info,", plid)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := app.AddPlaylist(pltitle, plid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error getting youtube playlist info,", plid)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
|
||||||
|
msg["event"] = "new_playlist"
|
||||||
|
msg["payload"] = map[string]string{"url": id.String(), "title": pltitle}
|
||||||
|
|
||||||
|
ws_msg <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) loadPlaylist(payload ...interface{}) {
|
||||||
|
log.Println("load_playlist event received")
|
||||||
|
|
||||||
|
if !(len(payload) > 0) {
|
||||||
|
log.Println("loadPlaylist called without a payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uuid.UUID
|
||||||
|
switch data := payload[0].(type) {
|
||||||
|
case json.RawMessage:
|
||||||
|
var err error
|
||||||
|
var tmp string
|
||||||
|
|
||||||
|
json.Unmarshal(data, &tmp)
|
||||||
|
id, err = uuid.Parse(tmp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to parse UUID,", err)
|
||||||
|
}
|
||||||
|
case uuid.UUID:
|
||||||
|
id = data
|
||||||
|
default:
|
||||||
|
log.Println("loadPlaylist called with invalid payload.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Loading new playlist: ", id)
|
||||||
|
pl, err := app.GetPlaylist(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to find playlist with id,", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.plm.Lock()
|
||||||
|
app.playlist = pl
|
||||||
|
app.plm.Unlock()
|
||||||
|
|
||||||
|
app.next = true
|
||||||
|
discordspeaker.Pause(false)
|
||||||
|
|
||||||
|
list, err := app.Playlist(pl.Url)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error getting playlist info,", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err = ShufflePlaylist(list)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to shuffle playlist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.active = list
|
||||||
|
|
||||||
|
song := app.GetNextSong(list)
|
||||||
|
f, err := ffmpeg.NewPCM(song, sampleRate, channels)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to start new ffmpeg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.queue.Reset()
|
||||||
|
app.queue.Add(f)
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
app.next = false
|
||||||
|
|
||||||
|
msg := app.songInfoEvent("song_info")
|
||||||
|
ws_msg <- msg
|
||||||
|
log.Println("Added song", song)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) preloadSong(payload ...interface{}) {
|
||||||
|
log.Println("preload_song event received")
|
||||||
|
app.next = true
|
||||||
|
discordspeaker.Pause(false)
|
||||||
|
|
||||||
|
var song string
|
||||||
|
switch current := app.queue.Current().(type) {
|
||||||
|
case *ffmpeg.PCM:
|
||||||
|
for {
|
||||||
|
song = app.GetNextSong(app.active)
|
||||||
|
if current.Uri != song {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Println("Got same song, try again!")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
song = app.GetNextSong(app.active)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := ffmpeg.NewPCM(song, sampleRate, channels)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
discordspeaker.Lock()
|
||||||
|
app.queue.Add(f)
|
||||||
|
discordspeaker.Unlock()
|
||||||
|
|
||||||
|
log.Println("Added song.", song)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dndmusicbot/ytdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FFmpeg struct {
|
||||||
|
Out *bytes.Buffer
|
||||||
|
Cmd *exec.Cmd
|
||||||
|
Started bool
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
Len time.Duration
|
||||||
|
Title string
|
||||||
|
Channel string
|
||||||
|
PMutex *sync.RWMutex
|
||||||
|
ProcessState *os.ProcessState
|
||||||
|
err chan error
|
||||||
|
fb chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error) {
|
||||||
|
var yt *ytdl.YTdl
|
||||||
|
for {
|
||||||
|
yt, err = ytdl.NewYTdl(uri)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Something went wrong, trying again.", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if yt.Url != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Something went wrong, trying again.")
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
RETRY:
|
||||||
|
ff = new(FFmpeg)
|
||||||
|
ff.PMutex = &sync.RWMutex{}
|
||||||
|
ff.Len = yt.Len
|
||||||
|
ff.Title = yt.Title
|
||||||
|
ff.Channel = yt.Channel
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ff.Cancel = cancel
|
||||||
|
|
||||||
|
ff.Cmd = exec.CommandContext(
|
||||||
|
ctx,
|
||||||
|
"ffmpeg",
|
||||||
|
"-xerror",
|
||||||
|
"-i", yt.Url,
|
||||||
|
"-f", "s16le",
|
||||||
|
"-v", "error",
|
||||||
|
// "-stats",
|
||||||
|
//"-progress", "pipe:2",
|
||||||
|
"-ar", strconv.Itoa(sampleRate),
|
||||||
|
"-ac", strconv.Itoa(channels),
|
||||||
|
"-af", "loudnorm=I=-16:LRA=11:TP=-1.5",
|
||||||
|
"pipe:1",
|
||||||
|
)
|
||||||
|
|
||||||
|
ff.Cmd.Stderr = os.Stdin
|
||||||
|
|
||||||
|
// FFmpeg requires a certain buffer size to start writing. This seems to be enough?
|
||||||
|
// This will grow big enough to fit the whole song.
|
||||||
|
ff.Out = bytes.NewBuffer(make([]byte, 43920))
|
||||||
|
ff.Cmd.Stdout = ff.Out
|
||||||
|
|
||||||
|
exitctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ff.Cmd.Start()
|
||||||
|
ff.err <- ff.Cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-exitctx.Done():
|
||||||
|
ff.Started = true
|
||||||
|
case <-ff.err:
|
||||||
|
log.Println("ffmpeg exited early, retry...")
|
||||||
|
goto RETRY
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ff *FFmpeg) Close() error {
|
||||||
|
ff.Cancel()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PCM struct {
|
||||||
|
f beep.Format
|
||||||
|
pos int
|
||||||
|
sr int
|
||||||
|
c int
|
||||||
|
Player *FFmpeg
|
||||||
|
Uri string
|
||||||
|
Base float64
|
||||||
|
Volume float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPCM(uri string, sampleRate int, channels int) (beep.StreamSeekCloser, error) {
|
||||||
|
out := new(PCM)
|
||||||
|
|
||||||
|
ff, err := NewFFmpeg(uri, sampleRate, channels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Player = ff
|
||||||
|
format := beep.Format{
|
||||||
|
SampleRate: beep.SampleRate(sampleRate),
|
||||||
|
NumChannels: channels,
|
||||||
|
Precision: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PCM{
|
||||||
|
format,
|
||||||
|
0,
|
||||||
|
sampleRate,
|
||||||
|
channels,
|
||||||
|
ff,
|
||||||
|
uri,
|
||||||
|
2,
|
||||||
|
-2,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PCM) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
|
tmp := make([]byte, d.c+2)
|
||||||
|
|
||||||
|
for i := range samples {
|
||||||
|
dn, err := d.Player.Out.Read(tmp)
|
||||||
|
if dn == len(tmp) {
|
||||||
|
samples[i], _ = d.f.DecodeSigned(tmp)
|
||||||
|
d.pos += dn
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
gain := math.Pow(d.Base, d.Volume)
|
||||||
|
samples[i][0] *= gain
|
||||||
|
samples[i][1] *= gain
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(samples), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d PCM) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d PCM) Position() int {
|
||||||
|
t, _ := time.ParseDuration(fmt.Sprintf("%ds", ((d.pos / d.sr) / 4)))
|
||||||
|
return int(t.Milliseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d PCM) Close() error {
|
||||||
|
d.Player.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d PCM) Len() int {
|
||||||
|
return int(d.Player.Len.Milliseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d PCM) Seek(p int) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
module dndmusicbot
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bwmarrin/discordgo v0.26.1
|
||||||
|
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8
|
||||||
|
github.com/faiface/beep v1.1.0
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d
|
||||||
|
github.com/jackc/pgx/v5 v5.1.0
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
github.com/kataras/go-events v0.0.3
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/r3labs/sse/v2 v2.8.2
|
||||||
|
github.com/spf13/viper v1.14.0
|
||||||
|
github.com/tidwall/gjson v1.14.3
|
||||||
|
google.golang.org/api v0.103.0
|
||||||
|
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.12.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||||
|
github.com/hajimehoshi/go-mp3 v0.3.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
|
golang.org/x/net v0.2.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||||
|
golang.org/x/sys v0.2.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
golang.org/x/time v0.2.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
|
||||||
|
google.golang.org/grpc v1.50.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||||
|
gopkg.in/fatih/set.v0 v0.2.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,586 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
|
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||||
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
|
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
|
||||||
|
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
|
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
|
||||||
|
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8 h1:t1Ox7k2+GSzIv3fihjV7YFGb40nb/e2oyrTM/ngbzbA=
|
||||||
|
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8/go.mod h1:QqXVl9BAyVoWIZE4oA9XfkwCjQ3JaajiX4vq7Zh8Vzs=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||||
|
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||||
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||||
|
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||||
|
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||||
|
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
||||||
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d h1:Q2+KsA/1GLC9xyLsDun3/EOJ+83rY/IHRsO1DToPrdo=
|
||||||
|
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d/go.mod h1:RInr+B3/Tx70hYm0rpNPMTD7vH0pBG5ny/JsHAs2KcQ=
|
||||||
|
github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g=
|
||||||
|
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||||
|
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||||
|
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||||
|
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgx/v5 v5.1.0 h1:Z7pLKUb65HK6m18No8GGKT87K34NhIIEHa86rRdjxbU=
|
||||||
|
github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
|
||||||
|
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||||
|
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||||
|
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||||
|
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/r3labs/sse/v2 v2.8.2 h1:YWZy2i2nLoD5fE3vLLTdTz/8wxIYIFp5XbLNmmrrNts=
|
||||||
|
github.com/r3labs/sse/v2 v2.8.2/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
|
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||||
|
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||||
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||||
|
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||||
|
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
|
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||||
|
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
|
||||||
|
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||||
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
|
||||||
|
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
|
google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
|
||||||
|
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
|
||||||
|
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
||||||
|
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
||||||
|
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY=
|
||||||
|
gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 h1:/S1gOotFo2sADAIdSGk1sDq1VxetoCWr6f5nxOG0dpY=
|
||||||
|
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32/go.mod h1:yDtyzWZDFCVnva8NGtg38eH2Ns4J0D/6hD+MMeUGdF0=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
@ -0,0 +1,227 @@
|
||||||
|
window.onload = function() {
|
||||||
|
const ws = new ReconnectingWebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws");
|
||||||
|
|
||||||
|
const items = document.querySelector("#items");
|
||||||
|
const amb = document.querySelector("#ambiance")
|
||||||
|
const submit = document.querySelector("#addplaylist")
|
||||||
|
const output = document.querySelector(".container2 p#output")
|
||||||
|
const info = document.querySelector("#info")
|
||||||
|
const waiting = document.querySelector("#waiting")
|
||||||
|
|
||||||
|
document.querySelector("input#next").addEventListener("click", (e) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": "next"
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelector("input#prev").addEventListener("click", (e) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": "prev"
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Sortable.create(items, {
|
||||||
|
group: "dndmusicbot-playlists",
|
||||||
|
filter: ".locked",
|
||||||
|
onMove: (e) => {
|
||||||
|
if (e.related)
|
||||||
|
{
|
||||||
|
return !e.related.classList.contains('locked');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invertSwap: true,
|
||||||
|
store: {
|
||||||
|
get: function(sortable) {
|
||||||
|
var order = localStorage.getItem(sortable.options.group.name);
|
||||||
|
return order ? order.split('|') : [];
|
||||||
|
},
|
||||||
|
set: function(sortable) {
|
||||||
|
var order = sortable.toArray();
|
||||||
|
localStorage.setItem(sortable.options.group.name, order.join('|'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Sortable.create(amb, {
|
||||||
|
group: "dndmusicbot-ambiance",
|
||||||
|
filter: ".locked",
|
||||||
|
onMove: (e) => {
|
||||||
|
if (e.related)
|
||||||
|
{
|
||||||
|
return !e.related.classList.contains('locked');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invertSwap: true,
|
||||||
|
store: {
|
||||||
|
get: function(sortable) {
|
||||||
|
var order = localStorage.getItem(sortable.options.group.name);
|
||||||
|
return order ? order.split('|') : [];
|
||||||
|
},
|
||||||
|
set: function(sortable) {
|
||||||
|
var order = sortable.toArray();
|
||||||
|
localStorage.setItem(sortable.options.group.name, order.join('|'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.onopen = (e) => {
|
||||||
|
waiting.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = (e) => {
|
||||||
|
waiting.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
data = JSON.parse(e.data)
|
||||||
|
switch (data.event) {
|
||||||
|
case "ambiance_play":
|
||||||
|
document.querySelectorAll("#ambiance > div").forEach((e) => {e.style.removeProperty("background-color")})
|
||||||
|
document.querySelector(`#ambiance > div[data-id='${data.payload.type}']`).style.backgroundColor = "burlywood"
|
||||||
|
ambiance.style.pointerEvents = 'auto'
|
||||||
|
break
|
||||||
|
case "ambiance_stop":
|
||||||
|
document.querySelectorAll("#ambiance > div").forEach((el) => {
|
||||||
|
el.style.removeProperty("background-color")
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "song_info":
|
||||||
|
document.querySelectorAll("#items > div").forEach((el) => {
|
||||||
|
el.style.removeProperty("background-color")
|
||||||
|
})
|
||||||
|
if (data.payload.pause) {
|
||||||
|
info.style.display = "none"
|
||||||
|
} else {
|
||||||
|
info.style.display = "block"
|
||||||
|
info.children.link.children.channel.innerText = data.payload.channel
|
||||||
|
info.children.link.children.title.innerText = data.payload.current
|
||||||
|
info.children.link.href = "https://youtu.be/" + data.payload.song
|
||||||
|
info.children.link.target = "_blank"
|
||||||
|
info.children.time.innerText = `( ${msToTime(data.payload.position)} / ${msToTime(data.payload.len)} )`
|
||||||
|
document.querySelector(`#items > div[data-id='${data.payload.playlist}']`).style.backgroundColor = "burlywood"
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
items.style.pointerEvents = 'auto'
|
||||||
|
}, 1000);
|
||||||
|
break
|
||||||
|
case "song_position":
|
||||||
|
info.children.time.innerText = `( ${msToTime(data.payload.position)} / ${msToTime(data.payload.len)} )`
|
||||||
|
info.style.display = "block"
|
||||||
|
break
|
||||||
|
case "stop":
|
||||||
|
setTimeout(() => {
|
||||||
|
items.style.pointerEvents = 'auto'
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
info.style.display = "none"
|
||||||
|
break
|
||||||
|
case "new_playlist":
|
||||||
|
addPlaylist(data.payload);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector("#addambiance").addEventListener("click", (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
output.innerText = ""
|
||||||
|
var title = document.querySelector("#inputambiance > input[name='title']")
|
||||||
|
var url = document.querySelector("#inputambiance > input[name='url']")
|
||||||
|
if (title.value == "" || url.value == "") {
|
||||||
|
output.innerText = "Title or Url is empty!"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": "ambiance_add",
|
||||||
|
"payload": {
|
||||||
|
"title": title.value,
|
||||||
|
"url": url.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
submit.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
output.innerText = ""
|
||||||
|
var title = document.querySelector("#inputplaylist > input[name='title']")
|
||||||
|
var url = document.querySelector("#inputplaylist > input[name='url']")
|
||||||
|
if (title.value == "" || url.value == "") {
|
||||||
|
output.innerText = "Title or Url is empty!"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": "add_playlist",
|
||||||
|
"payload": {
|
||||||
|
"title": title.value,
|
||||||
|
"url": url.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
title.innerText = ""
|
||||||
|
url.innerText = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelectorAll("#items").forEach(item => item.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.target.parentElement.style.pointerEvents = 'none'
|
||||||
|
const disableui = setTimeout((t) => {
|
||||||
|
t.style.pointerEvents = 'auto'
|
||||||
|
}, 3000, e.target.parentElement);
|
||||||
|
|
||||||
|
var id = e.target.dataset.id
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": ((id === "reset") ? "stop" : "load_playlist"),
|
||||||
|
"payload": id
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
document.querySelectorAll("#ambiance").forEach(item => item.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.target.parentElement.style.pointerEvents = 'none'
|
||||||
|
const disableui = setTimeout((t) => {
|
||||||
|
t.style.pointerEvents = 'auto'
|
||||||
|
}, 3000, e.target.parentElement);
|
||||||
|
|
||||||
|
var id = e.target.dataset.id
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": ((id === "reset") ? "ambiance_stop" : "ambiance_play"),
|
||||||
|
"payload": id
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
function addPlaylist(payload) {
|
||||||
|
const container = document.querySelector("body > div.container")
|
||||||
|
var newdiv = document.createElement('div');
|
||||||
|
newdiv.className = "item"
|
||||||
|
newdiv.dataset.id = payload.url
|
||||||
|
newdiv.innerText = payload.title
|
||||||
|
newdiv.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
var id = e.target.dataset.id
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": ((id === "reset") ? "stop" : "load_playlist"),
|
||||||
|
"payload": id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
container.insertBefore(newdiv, document.querySelector("body > div.container div:last-child"))
|
||||||
|
|
||||||
|
output.innerText = "New playlist was added: " + payload.title
|
||||||
|
}
|
||||||
|
|
||||||
|
function msToTime(duration) {
|
||||||
|
var milliseconds = parseInt((duration % 1000) / 100),
|
||||||
|
seconds = Math.floor((duration / 1000) % 60),
|
||||||
|
minutes = Math.floor((duration / (1000 * 60)) % 60),
|
||||||
|
|
||||||
|
minutes = (minutes < 10) ? "0" + minutes : minutes;
|
||||||
|
seconds = (seconds < 10) ? "0" + seconds : seconds;
|
||||||
|
|
||||||
|
return minutes + ":" + seconds
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package loop
|
||||||
|
|
||||||
|
import "github.com/faiface/beep"
|
||||||
|
|
||||||
|
type loop struct {
|
||||||
|
s beep.StreamSeekCloser
|
||||||
|
remains int
|
||||||
|
}
|
||||||
|
|
||||||
|
func Loop(count int, s beep.StreamSeekCloser) beep.StreamSeekCloser {
|
||||||
|
return &loop{
|
||||||
|
s: s,
|
||||||
|
remains: count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
|
if l.remains == 0 || l.s.Err() != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
for len(samples) > 0 {
|
||||||
|
sn, sok := l.s.Stream(samples)
|
||||||
|
if !sok {
|
||||||
|
if l.remains > 0 {
|
||||||
|
l.remains--
|
||||||
|
}
|
||||||
|
if l.remains == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err := l.s.Seek(0)
|
||||||
|
if err != nil {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
samples = samples[sn:]
|
||||||
|
n += sn
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Close() error {
|
||||||
|
return l.s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Len() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Position() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Seek(p int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loop) Err() error {
|
||||||
|
return l.s.Err()
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
|
||||||
|
discordspeaker "dndmusicbot/speaker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ambiance struct {
|
||||||
|
Type string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
app.ambiance = new(Queue)
|
||||||
|
app.ambiance.Events = app.events
|
||||||
|
discordspeaker.Play(app.ambiance)
|
||||||
|
|
||||||
|
app.queue = new(Queue)
|
||||||
|
app.queue.Events = app.events
|
||||||
|
discordspeaker.Play(app.queue)
|
||||||
|
|
||||||
|
log.Println("queue.go done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queue struct {
|
||||||
|
streamers []beep.StreamSeekCloser
|
||||||
|
Events events.EventEmmiter
|
||||||
|
playing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Queue) IsPlaying() bool {
|
||||||
|
return q.playing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Add(streamers ...beep.StreamSeekCloser) {
|
||||||
|
q.playing = true
|
||||||
|
q.streamers = append(q.streamers, streamers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Queue) QLen() int {
|
||||||
|
return len(q.streamers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Queue) Len() int {
|
||||||
|
if len(q.streamers) > 0 {
|
||||||
|
return q.streamers[0].Len()
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Queue) Current() beep.StreamSeekCloser {
|
||||||
|
if len(q.streamers) > 0 {
|
||||||
|
return q.streamers[0]
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Reset() {
|
||||||
|
q.playing = false
|
||||||
|
for _, stream := range q.streamers {
|
||||||
|
stream.Close()
|
||||||
|
}
|
||||||
|
q.streamers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
|
// We use the filled variable to track how many samples we've
|
||||||
|
// successfully filled already. We loop until all samples are filled.
|
||||||
|
filled := 0
|
||||||
|
|
||||||
|
for filled < len(samples) {
|
||||||
|
// There are no streamers in the queue, so we stream silence.
|
||||||
|
if len(q.streamers) == 0 {
|
||||||
|
for i := range samples[filled:] {
|
||||||
|
samples[i][0] = 0
|
||||||
|
samples[i][1] = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// We stream from the first streamer in the queue.
|
||||||
|
n, ok := q.streamers[0].Stream(samples[filled:])
|
||||||
|
// If it's drained, we pop it from the queue, thus continuing with
|
||||||
|
// the next streamer.
|
||||||
|
if !ok {
|
||||||
|
q.streamers = q.streamers[1:]
|
||||||
|
q.Events.Emit("song_over", nil)
|
||||||
|
}
|
||||||
|
// We update the number of filled samples.
|
||||||
|
filled += n
|
||||||
|
}
|
||||||
|
return len(samples), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Position() int {
|
||||||
|
if len(q.streamers) > 0 {
|
||||||
|
return q.streamers[0].Position()
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
app.router = httprouter.New()
|
||||||
|
app.router.GET("/", app.Index)
|
||||||
|
app.router.GET("/play/:playlist", app.Play)
|
||||||
|
app.router.GET("/reset", app.Reset)
|
||||||
|
|
||||||
|
app.router.ServeFiles("/js/*filepath", http.Dir("js"))
|
||||||
|
app.router.ServeFiles("/css/*filepath", http.Dir("css"))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Fatal(http.ListenAndServe(":8824", app.router))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndexData struct {
|
||||||
|
Playlists []Playlist
|
||||||
|
Ambiance []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app App) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
playlists, err := app.GetPlaylists()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
amblist, err := GetAmbiance()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := IndexData{playlists, amblist}
|
||||||
|
|
||||||
|
t := template.Must(template.New("index.tmpl").ParseFiles("tmpl/index.tmpl"))
|
||||||
|
err = t.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Unable to load template. "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Play(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
plname := p.ByName("playlist")
|
||||||
|
|
||||||
|
if plname == "reset" {
|
||||||
|
app.events.Emit("stop", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plid, err := uuid.ParseBytes([]byte(plname))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Unable to parse uuid. "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.events.Emit("new_playlist", plid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Add(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Reset(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
app.events.Emit("stop", nil)
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package discordspeaker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"layeh.com/gopus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bufferSize = 1000
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
mixer beep.Mixer
|
||||||
|
samples [][2]float64
|
||||||
|
done chan struct{}
|
||||||
|
encoder *gopus.Encoder
|
||||||
|
voice *discordgo.VoiceConnection
|
||||||
|
frameSize int = 960
|
||||||
|
channels int = 2
|
||||||
|
sampleRate int = 48000
|
||||||
|
maxBytes int = (frameSize * 2) * 2
|
||||||
|
pcm []int16
|
||||||
|
buf []byte
|
||||||
|
pause bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(dgv *discordgo.VoiceConnection) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
Close()
|
||||||
|
mixer = beep.Mixer{}
|
||||||
|
|
||||||
|
buf = make([]byte, maxBytes)
|
||||||
|
pcm = make([]int16, frameSize*channels)
|
||||||
|
samples = make([][2]float64, frameSize)
|
||||||
|
|
||||||
|
pause = true
|
||||||
|
|
||||||
|
voice = dgv
|
||||||
|
|
||||||
|
encoder, err = gopus.NewEncoder(sampleRate, channels, gopus.Audio)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to initialize speaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
update()
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lock() {
|
||||||
|
mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
|
||||||
|
func Unlock() {
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Play(s ...beep.Streamer) {
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Add(s...)
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clear() {
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Clear()
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pause(p bool) {
|
||||||
|
pause = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPaused() bool {
|
||||||
|
return pause
|
||||||
|
}
|
||||||
|
|
||||||
|
func update() {
|
||||||
|
if pause {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Stream(samples)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
for i := range samples {
|
||||||
|
for c := range samples[i] {
|
||||||
|
val := samples[i][c]
|
||||||
|
if val < -1 {
|
||||||
|
val = -1
|
||||||
|
}
|
||||||
|
if val > +1 {
|
||||||
|
val = +1
|
||||||
|
}
|
||||||
|
valInt16 := int16(val * (1<<15 - 1))
|
||||||
|
low := byte(valInt16)
|
||||||
|
high := byte(valInt16 >> 8)
|
||||||
|
buf[i*4+c*2+0] = low
|
||||||
|
buf[i*4+c*2+1] = high
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Read(bytes.NewReader(buf), binary.LittleEndian, &pcm)
|
||||||
|
opus, err := encoder.Encode(pcm, frameSize, maxBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if voice.Ready == false || voice.OpusSend == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
voice.OpusSend <- opus
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>D&D Music Bot!</title>
|
||||||
|
<link rel="stylesheet" href="/css/solarized-dark.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div><h2 class="bot">Playlists</h2></div>
|
||||||
|
<div id="items" class="container">
|
||||||
|
{{ range .Playlists }}
|
||||||
|
<div class="item" data-id="{{ .Id }}">{{ .Title }}</div>
|
||||||
|
{{ end}}
|
||||||
|
<div class="item locked" data-id="reset">Stop</div>
|
||||||
|
</div>
|
||||||
|
<div id="inputplaylist" class="container2">
|
||||||
|
<input name="title" type="text" placeholder="Enter name..">
|
||||||
|
<input name="url" type="text" placeholder="https://youtube.com/playlist?list=...">
|
||||||
|
<input id="addplaylist" name="submit" value="Add" type="submit">
|
||||||
|
</div>
|
||||||
|
<div class="container2">
|
||||||
|
<p id="output"></p>
|
||||||
|
</div>
|
||||||
|
<div id="info" class="container2">
|
||||||
|
<input id="prev" name="prev" type="button" value="prev">
|
||||||
|
<input id="next" name="next" type="button" value="next">
|
||||||
|
<a id="link" style="text-decoration: none;">
|
||||||
|
<span id="channel"></span>
|
||||||
|
<span> - </span>
|
||||||
|
<span id="title"></span>
|
||||||
|
</a>
|
||||||
|
<span id="time"></span>
|
||||||
|
</div>
|
||||||
|
<div><h2 class="bot">Ambiance</h2></div>
|
||||||
|
<div id="ambiance" class="container">
|
||||||
|
{{ range .Ambiance }}
|
||||||
|
<div class="item drag" data-id="{{ . }}">{{ . }}</div>
|
||||||
|
{{ end}}
|
||||||
|
<div class="item locked" data-id="reset">Stop</div>
|
||||||
|
</div>
|
||||||
|
<div id="inputambiance" class="container2">
|
||||||
|
<input name="title" type="text" placeholder="Enter name..">
|
||||||
|
<input name="url" type="text" placeholder="Enter url...">
|
||||||
|
<input id="addambiance" name="submit" value="Add" type="submit">
|
||||||
|
</div>
|
||||||
|
<!-- The Modal -->
|
||||||
|
<div id="waiting" class="modal">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<div class="modal-content">
|
||||||
|
<p>Waiting for bot..</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/js/reconnecting-websocket.min.js"></script>
|
||||||
|
<script src="/js/Sortable.min.js"></script>
|
||||||
|
<script src="/js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/grafov/bcast"
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("ws.go loading..")
|
||||||
|
go ws_clients.Broadcast(0)
|
||||||
|
ws_msg = make(chan interface{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-ws_msg:
|
||||||
|
ws_clients.Send(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Since httprouter seems to not like doing websocket stuff, we run a seperate server for it.. for now..
|
||||||
|
go func() {
|
||||||
|
app.mux.HandleFunc("/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Printf("WS connection from %v\n", r.RemoteAddr)
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handleWS(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("WS connection closed, %v\n", r.RemoteAddr)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("ws.go done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type WSmsg struct {
|
||||||
|
Event string
|
||||||
|
Payload json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws_clients = bcast.NewGroup()
|
||||||
|
var ws_msg chan interface{}
|
||||||
|
var WSMutex = &sync.Mutex{}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWS(c *websocket.Conn) error {
|
||||||
|
memb := ws_clients.Join()
|
||||||
|
defer memb.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
c.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second))
|
||||||
|
case msg := <-memb.Read:
|
||||||
|
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
c.WriteJSON(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.SetPongHandler(func(d string) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
msg := app.songInfoEvent("song_info")
|
||||||
|
|
||||||
|
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
c.WriteJSON(msg)
|
||||||
|
|
||||||
|
if app.ambiance.IsPlaying() {
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_play"
|
||||||
|
out["type"] = app.curamb
|
||||||
|
msg["payload"] = out
|
||||||
|
c.WriteJSON(msg)
|
||||||
|
} else {
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_stop"
|
||||||
|
c.WriteJSON(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var msg WSmsg
|
||||||
|
err := c.ReadJSON(&msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
app.events.Emit(events.EventName(msg.Event), msg.Payload)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mrand "math/rand"
|
||||||
|
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"google.golang.org/api/youtube/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
yt_url = "https://www.youtube.com/watch?v=%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Println("youtube.go loading..")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
apikey := config.GetString("youtube.apikey")
|
||||||
|
|
||||||
|
app.youtube, err = youtube.NewService(context.Background(), option.WithAPIKey(apikey))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("youtube.go done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type YT struct {
|
||||||
|
dst io.WriteCloser
|
||||||
|
pcm io.ReadCloser
|
||||||
|
dur time.Duration
|
||||||
|
pos time.Duration
|
||||||
|
f beep.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShufflePlaylist(list []string) ([]string, error) {
|
||||||
|
seedb := make([]byte, 32)
|
||||||
|
_, err := rand.Read(seedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
seed := binary.BigEndian.Uint64(seedb)
|
||||||
|
mrand.Seed(int64(seed))
|
||||||
|
mrand.Shuffle(len(list), func(i, j int) { list[i], list[j] = list[j], list[i] })
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) GetSong(list []string) string {
|
||||||
|
return fmt.Sprintf(yt_url, list[app.plidx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) GetNextSong(list []string) string {
|
||||||
|
app.plidx++
|
||||||
|
if app.plidx >= len(app.active) {
|
||||||
|
app.plidx = 0
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(yt_url, list[app.plidx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) GetPrevSong(list []string) string {
|
||||||
|
app.plidx--
|
||||||
|
if app.plidx < 0 {
|
||||||
|
app.plidx = len(list)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(yt_url, list[app.plidx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRandomSong(list []string) string {
|
||||||
|
if !(len(list) > 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(list)-1)))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to get random int, ", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(yt_url, list[idx.Int64()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app App) Playlist(playlist string) ([]string, error) {
|
||||||
|
call := app.youtube.PlaylistItems.List([]string{"contentDetails"})
|
||||||
|
pageToken := ""
|
||||||
|
call = call.MaxResults(50)
|
||||||
|
call = call.PlaylistId(playlist)
|
||||||
|
if pageToken != "" {
|
||||||
|
call = call.PageToken(pageToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []string
|
||||||
|
for {
|
||||||
|
response, err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, item := range response.Items {
|
||||||
|
list = append(list, item.ContentDetails.VideoId)
|
||||||
|
}
|
||||||
|
pageToken = response.NextPageToken
|
||||||
|
if pageToken == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
|
||||||
|
//return fmt.Sprintf(yt_url, list[rand.Intn(len(list))])
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package ytdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YTdl struct {
|
||||||
|
Title string
|
||||||
|
Url string
|
||||||
|
Channel string
|
||||||
|
Len time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewYTdl(uri string) (*YTdl, error) {
|
||||||
|
ytdl_js, err := exec.Command(
|
||||||
|
"./bin/yt-dlp_linux",
|
||||||
|
uri,
|
||||||
|
"--cookies", "./cookies.txt",
|
||||||
|
"--no-call-home",
|
||||||
|
"--no-cache-dir",
|
||||||
|
"--ignore-errors",
|
||||||
|
"--newline",
|
||||||
|
"--restrict-filenames",
|
||||||
|
"-f", "140",
|
||||||
|
"-j",
|
||||||
|
).Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !gjson.ValidBytes(ytdl_js) {
|
||||||
|
return nil, errors.New("invalid json")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := gjson.GetManyBytes(ytdl_js, "title", "url", "duration", "channel")
|
||||||
|
title := results[0].String()
|
||||||
|
geturl := results[1].String()
|
||||||
|
duration, err := time.ParseDuration(fmt.Sprintf("%ds", results[2].Int()))
|
||||||
|
channel := results[3].String()
|
||||||
|
|
||||||
|
return &YTdl{title, geturl, channel, duration}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownloadAmbiance(uri string, name string) error {
|
||||||
|
err := exec.Command(
|
||||||
|
"./bin/yt-dlp_linux",
|
||||||
|
uri,
|
||||||
|
"-x",
|
||||||
|
"--audio-format", "mp3",
|
||||||
|
"--postprocessor-args", "-ar 48000 -ac 2",
|
||||||
|
"--cookies", "./cookies.txt",
|
||||||
|
"--no-call-home",
|
||||||
|
"--no-cache-dir",
|
||||||
|
"--restrict-filenames",
|
||||||
|
"-f", "140",
|
||||||
|
"-o", filepath.Join("./ambiance/", name+".mp3"),
|
||||||
|
).Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue