Compare commits

...

5 Commits

18 changed files with 781 additions and 356 deletions

5
.gitignore vendored
View File

@ -7,6 +7,11 @@ cookies.txt
config config
css/*.min.css css/*.min.css
js/*.min.js js/*.min.js
cache
bin bin
ambiance/*.mp3 ambiance/*.mp3
*.yml
public/
node_modules/

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
# docker build -t dndmusicbot-js-build .
# docker run -it -v $(pwd)/public:/app/public dndmusicbot-js-build
FROM node:19-alpine3.16
WORKDIR /app
COPY . /app
RUN yarn
ENTRYPOINT ["yarn", "build"]

9
bot.go
View File

@ -11,10 +11,13 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/dpup/gohubbub" "github.com/dpup/gohubbub"
"github.com/faiface/beep"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/kataras/go-events" "github.com/kataras/go-events"
"github.com/r3labs/sse/v2" "github.com/r3labs/sse/v2"
"github.com/spf13/afero"
"github.com/spf13/viper" "github.com/spf13/viper"
"google.golang.org/api/youtube/v3" "google.golang.org/api/youtube/v3"
) )
@ -48,7 +51,7 @@ type App struct {
voice *discordgo.VoiceConnection voice *discordgo.VoiceConnection
youtube *youtube.Service youtube *youtube.Service
queue *Queue queue *Queue
ambiance *Queue ambiance beep.Mixer
curamb string curamb string
events events.EventEmmiter events events.EventEmmiter
next bool next bool
@ -60,9 +63,13 @@ type App struct {
playlist *Playlist playlist *Playlist
plm *sync.RWMutex plm *sync.RWMutex
hubbub *gohubbub.Client hubbub *gohubbub.Client
cache *filecache.Cache
} }
func main() { func main() {
bfs := afero.NewBasePathFs(afero.NewOsFs(), "cache")
app.cache = filecache.NewCache(bfs, 1*time.Hour, "")
app.plm = &sync.RWMutex{} app.plm = &sync.RWMutex{}
ticker := time.NewTicker(300 * time.Millisecond) ticker := time.NewTicker(300 * time.Millisecond)

View File

@ -1,41 +1,69 @@
.container { .container {
display: grid; --grid-layout-gap: 8px;
grid-template-columns: 1fr 1fr 1fr; --grid-column-count: 3;
grid-template-rows: auto; --grid-item--min-width: 150px;
padding: 10px;
border-radius: 10px; --gap-count: calc(var(--grid-column-count) - 1);
--total-gap-width: calc(var(--gap-count) * var(--grid-layout-gap));
--grid-item--max-width: calc((100% - var(--total-gap-width)) / var(--grid-column-count));
display: grid;
grid-template-columns: repeat(auto-fill, minmax(max(var(--grid-item--min-width), var(--grid-item--max-width)), 1fr));
grid-auto-rows: 1fr;
grid-gap: var(--grid-layout-gap);
} }
.playing { .playing {
background-color: burlywood; background-color: burlywood;
} }
h2.bot { h2.bot {
margin-top: 10px; margin-top: 0;
margin-bottom: 0px; margin-bottom: .5rem;
text-align: center; text-align: center;
} }
.item { .item {
background-color: #80cbc4; display: flex;
border: 1px solid black; justify-content: center;
padding: 20px; align-items: center;
font-size: clamp(1rem, -0.8750rem + 8.3333vw, 2rem); padding: 1rem;
text-align: center; background-color: #80cbc4;
cursor: pointer; border: 1px solid black;
color: #073642 font-size: clamp(1rem, -0.8750rem + 8.3333vw, 2rem);
text-align: center;
cursor: pointer;
color: #073642
} }
.container2 { .item.stop {
display: block; background-color: #cb4b16;
border-radius: 10px; }
padding: 10px;
#info {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.controls {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
margin: 0 auto;
max-width: 512px;
}
.input-container {
max-width: 512px;
} }
.box { .box {
/* background-color: #fefefe;*/ /* background-color: #fefefe;*/
margin: 15% auto; /* 15% from the top and centered */ margin: 15% auto; /* 15% from the top and centered */
padding: 20px; padding: 2rem;
/*border: 1px solid #888;*/ /*border: 1px solid #888;*/
width: 80%; /* Could be more or less, depending on screen size */ width: 80%; /* Could be more or less, depending on screen size */
align-content: center; align-content: center;
@ -58,12 +86,72 @@ h2.bot {
/* Modal Content/Box */ /* Modal Content/Box */
.modal-content { .modal-content {
margin: 15% auto; /* 15% from the top and centered */ margin: 15% auto; /* 15% from the top and centered */
padding: 20px; padding: 2rem;
width: 80%; /* Could be more or less, depending on screen size */ width: 80%; /* Could be more or less, depending on screen size */
text-align: center; text-align: center;
font-size: 32px; font-size: 32px;
} }
body > div.container2 > input[type=text]:nth-child(2) { body, html {
width: 512px; margin: 0;
padding: .5rem;
}
input[type="button"],
input[type="submit"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box;
}
input[type="button"]:focus,
input[type="submit"]:hover {
color: #333;
border-color: #888;
outline: 0;
}
input[type="text"] {
height: 38px;
padding: 6px 10px;
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
input[type="text"]:focus {
border: 1px solid #33C3F0;
outline: 0;
}
section {
margin-bottom: .5rem;
}
.u-full-width {
width: 100%;
box-sizing: border-box;
}
.u-hidden {
display: none;
} }

176
events.go
View File

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"dndmusicbot/ffmpeg" "dndmusicbot/ffmpeg"
"dndmusicbot/loop"
discordspeaker "dndmusicbot/speaker" discordspeaker "dndmusicbot/speaker"
"dndmusicbot/ytdl" "dndmusicbot/ytdl"
"encoding/json" "encoding/json"
@ -13,6 +12,7 @@ import (
"os" "os"
"time" "time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3" "github.com/faiface/beep/mp3"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kataras/go-events" "github.com/kataras/go-events"
@ -20,14 +20,14 @@ import (
) )
type SongInfo struct { type SongInfo struct {
Playlist uuid.UUID `json:"playlist"` Playlist uuid.UUID `json:"playlist,omitempty"`
PlaylistName string `json:"playlistname"` PlaylistName string `json:"playlistname,omitempty"`
Title string `json:"current"` Title string `json:"current,omitempty"`
Channel string `json:"channel"` Channel string `json:"channel,omitempty"`
Position int `json:"position"` Position int `json:"position"`
Length int `json:"len"` Length int `json:"len,omitempty"`
Pause bool `json:"pause"` Pause bool `json:"pause"`
Song string `json:"song"` Song string `json:"song,omitempty"`
} }
var l = rate.Sometimes{Interval: 1 * time.Second} var l = rate.Sometimes{Interval: 1 * time.Second}
@ -39,8 +39,9 @@ func init() {
app.events.On("load_playlist", app.loadPlaylist) app.events.On("load_playlist", app.loadPlaylist)
app.events.On("add_playlist", app.addPlaylist) app.events.On("add_playlist", app.addPlaylist)
app.events.On("preload_song", app.preloadSong) //app.events.On("preload_song", app.preloadSong)
app.events.On("song_over", app.songOver) app.events.On("song_over", app.songInfo)
app.events.On("song_start", app.songInfo)
//app.events.On("song_position", app.songPosition) //app.events.On("song_position", app.songPosition)
app.events.On("ambiance_play", app.ambiancePlay) app.events.On("ambiance_play", app.ambiancePlay)
@ -60,37 +61,15 @@ func (app *App) songInfoEvent(event string) map[string]interface{} {
msg := make(map[string]interface{}) msg := make(map[string]interface{})
msg["event"] = event 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{ msg["payload"] = SongInfo{
Playlist: plid, Playlist: app.queue.Current().Playlist.Id,
PlaylistName: pltitle, PlaylistName: app.queue.Current().Playlist.Title,
Title: title, Title: app.queue.Current().Title,
Channel: channel, Channel: app.queue.Current().Channel,
Position: app.queue.Position(), Position: 0,
Length: app.queue.Len(), Length: app.queue.Len(),
Pause: !app.queue.IsPlaying(), Pause: !app.queue.IsPlaying(),
Song: song, Song: app.queue.Current().VideoID,
} }
return msg return msg
@ -128,11 +107,8 @@ func (app *App) ambiancePlay(payload ...interface{}) {
discordspeaker.Pause(false) discordspeaker.Pause(false)
discordspeaker.Lock() discordspeaker.Lock()
if app.ambiance.IsPlaying() { app.ambiance.Clear()
app.ambiance.Reset() loop := beep.Loop(-1, play)
}
loop := loop.Loop(-1, play)
app.ambiance.Add(loop) app.ambiance.Add(loop)
discordspeaker.Unlock() discordspeaker.Unlock()
@ -150,7 +126,7 @@ func (app *App) ambiancePlay(payload ...interface{}) {
func (app *App) ambianceStop(payload ...interface{}) { func (app *App) ambianceStop(payload ...interface{}) {
log.Println("Stopping ambiance") log.Println("Stopping ambiance")
discordspeaker.Lock() discordspeaker.Lock()
app.ambiance.Reset() app.ambiance.Clear()
discordspeaker.Unlock() discordspeaker.Unlock()
msg := make(map[string]interface{}) msg := make(map[string]interface{})
@ -211,10 +187,8 @@ func (app *App) songPosition(payload ...interface{}) {
out := make(map[string]interface{}) out := make(map[string]interface{})
msg["event"] = "song_position" msg["event"] = "song_position"
if app.queue != nil { out["len"] = app.queue.Len()
out["len"] = app.queue.Len() out["position"] = app.queue.Position()
out["position"] = app.queue.Position()
}
msg["payload"] = out msg["payload"] = out
ws_msg <- msg ws_msg <- msg
@ -255,7 +229,7 @@ func (app *App) checkTimeleft(payload ...interface{}) {
} }
} }
func (app *App) songOver(payload ...interface{}) { func (app *App) songInfo(payload ...interface{}) {
log.Println("song_over event received") log.Println("song_over event received")
msg := app.songInfoEvent("song_info") msg := app.songInfoEvent("song_info")
@ -279,17 +253,8 @@ func (app *App) stop(payload ...interface{}) {
func (app *App) prevSong(payload ...interface{}) { func (app *App) prevSong(payload ...interface{}) {
log.Println("prev_song event received") 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.Prev()
app.queue.Reset()
app.queue.Add(f)
discordspeaker.Unlock()
msg := app.songInfoEvent("song_info") msg := app.songInfoEvent("song_info")
ws_msg <- msg ws_msg <- msg
@ -297,17 +262,8 @@ func (app *App) prevSong(payload ...interface{}) {
func (app *App) nextSong(payload ...interface{}) { func (app *App) nextSong(payload ...interface{}) {
log.Println("next_song event received") 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.Next()
app.queue.Reset()
app.queue.Add(f)
discordspeaker.Unlock()
msg := app.songInfoEvent("song_info") msg := app.songInfoEvent("song_info")
ws_msg <- msg ws_msg <- msg
@ -404,13 +360,7 @@ func (app *App) loadPlaylist(payload ...interface{}) {
return return
} }
app.plm.Lock()
app.playlist = pl
app.plm.Unlock()
app.next = true
discordspeaker.Pause(false) discordspeaker.Pause(false)
list, err := app.Playlist(pl.Url) list, err := app.Playlist(pl.Url)
if err != nil { if err != nil {
log.Println("Error getting playlist info,", id) log.Println("Error getting playlist info,", id)
@ -423,54 +373,44 @@ func (app *App) loadPlaylist(payload ...interface{}) {
return 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.Reset()
app.queue.Add(f)
discordspeaker.Unlock()
app.next = false go func() {
for _, vid := range list {
ytinfo, err := app.Video(vid)
if err != nil {
log.Println(err)
continue
}
msg := app.songInfoEvent("song_info") _, yt, err := app.cache.GetOrCreateBytes(vid+".txt", func() ([]byte, error) {
ws_msg <- msg uri, err := ytdl.NewYTdl(vid)
log.Println("Added song", song) if err != nil {
return nil, err
}
return uri, nil
})
ff, err := ffmpeg.NewPCM(string(yt), sampleRate, channels)
if err != nil {
log.Println(err)
continue
}
song := &Song{
Title: ytinfo.Title,
Channel: ytinfo.Channel,
VideoID: vid,
Length: ytinfo.Len,
PCM: ff,
Playlist: *pl,
DLuri: string(yt),
}
app.queue.Add(song)
}
}()
} }
func (app *App) preloadSong(payload ...interface{}) { func (app *App) preloadSong(payload ...interface{}) {
log.Println("preload_song event received") app.queue.Preload()
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)
} }

View File

@ -3,64 +3,36 @@ package ffmpeg
import ( import (
"bytes" "bytes"
"context" "context"
"log"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
"sync"
"time" "time"
"dndmusicbot/ytdl"
) )
type FFmpeg struct { type FFmpeg struct {
Out *bytes.Buffer Out *bytes.Buffer
Cmd *exec.Cmd Cmd *exec.Cmd
Started bool Started bool
Cancel context.CancelFunc Cancel context.CancelFunc
Len time.Duration Len time.Duration
Title string Title string
Channel string Channel string
PMutex *sync.RWMutex err chan error
ProcessState *os.ProcessState fb chan bool
err chan error
fb chan bool
} }
func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error) { 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 = new(FFmpeg)
ff.PMutex = &sync.RWMutex{}
ff.Len = yt.Len
ff.Title = yt.Title
ff.Channel = yt.Channel
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
ff.Cancel = cancel ff.Cancel = cancel
ff.Cmd = exec.CommandContext( ff.Cmd = exec.CommandContext(
ctx, ctx,
"ffmpeg", "ffmpeg",
"-xerror", "-i", uri,
"-i", yt.Url,
"-f", "s16le", "-f", "s16le",
"-v", "error", "-v", "error",
// "-stats", //"-stats",
//"-progress", "pipe:2",
"-ar", strconv.Itoa(sampleRate), "-ar", strconv.Itoa(sampleRate),
"-ac", strconv.Itoa(channels), "-ac", strconv.Itoa(channels),
"-af", "loudnorm=I=-16:LRA=11:TP=-1.5", "-af", "loudnorm=I=-16:LRA=11:TP=-1.5",
@ -74,26 +46,22 @@ RETRY:
ff.Out = bytes.NewBuffer(make([]byte, 128*1024)) ff.Out = bytes.NewBuffer(make([]byte, 128*1024))
ff.Cmd.Stdout = ff.Out 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 return
} }
func (ff *FFmpeg) Close() error { func (ff *FFmpeg) Start() error {
ff.Started = true
err := ff.Cmd.Start()
go func() {
if err != nil {
ff.err <- ff.Cmd.Wait()
}
}()
return err
}
func (ff FFmpeg) Close() error {
ff.Cancel() ff.Cancel()
return nil return nil
} }

View File

@ -3,6 +3,7 @@ package ffmpeg
import ( import (
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
"time" "time"
@ -20,7 +21,7 @@ type PCM struct {
Volume float64 Volume float64
} }
func NewPCM(uri string, sampleRate int, channels int) (beep.StreamSeekCloser, error) { func NewPCM(uri string, sampleRate int, channels int) (*PCM, error) {
out := new(PCM) out := new(PCM)
ff, err := NewFFmpeg(uri, sampleRate, channels) ff, err := NewFFmpeg(uri, sampleRate, channels)
@ -62,6 +63,7 @@ func (d *PCM) Stream(samples [][2]float64) (n int, ok bool) {
break break
} }
if err != nil { if err != nil {
log.Println(err)
ok = false ok = false
break break
} }

34
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/bwmarrin/discordgo v0.26.1 github.com/bwmarrin/discordgo v0.26.1
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8 github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8
github.com/faiface/beep v1.1.0 github.com/faiface/beep v1.1.0
github.com/gohugoio/hugo v0.106.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d
@ -14,8 +15,11 @@ require (
github.com/kataras/go-events v0.0.3 github.com/kataras/go-events v0.0.3
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/r3labs/sse/v2 v2.8.2 github.com/r3labs/sse/v2 v2.8.2
github.com/sosodev/duration v1.0.1
github.com/spf13/afero v1.9.3
github.com/spf13/viper v1.14.0 github.com/spf13/viper v1.14.0
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
golang.org/x/time v0.2.0
google.golang.org/api v0.103.0 google.golang.org/api v0.103.0
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32
) )
@ -23,7 +27,23 @@ require (
require ( require (
cloud.google.com/go/compute v1.12.1 // indirect cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect cloud.google.com/go/compute/metadata v0.2.1 // indirect
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alecthomas/chroma/v2 v2.3.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/bep/clock v0.3.0 // indirect
github.com/bep/debounce v1.2.0 // indirect
github.com/bep/godartsass v0.14.0 // indirect
github.com/bep/golibsass v1.1.0 // indirect
github.com/bep/overlayfs v0.6.0 // indirect
github.com/clbanning/mxj/v2 v2.5.7 // indirect
github.com/cli/safeexec v1.0.0 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gohugoio/locales v0.14.0 // indirect
github.com/gohugoio/localescompressed v1.0.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
@ -32,24 +52,32 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jdkato/prose v1.2.1 // indirect
github.com/kyokomi/emoji/v2 v2.2.10 // indirect
github.com/magiconair/properties v1.8.6 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/niklasfasching/go-org v1.6.5 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/spf13/afero v1.9.2 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect
github.com/tdewolff/parse/v2 v2.6.4 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/yuin/goldmark v1.5.3 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.2.0 // indirect golang.org/x/crypto v0.2.0 // indirect
golang.org/x/net 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/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.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/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
google.golang.org/grpc v1.50.1 // indirect google.golang.org/grpc v1.50.1 // indirect

118
go.sum
View File

@ -42,23 +42,58 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 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= 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= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M=
github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk=
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
github.com/bep/godartsass v0.14.0 h1:pPb6XkpyDEppS+wK0veh7OXDQc4xzOJI9Qcjb743UeQ=
github.com/bep/godartsass v0.14.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw=
github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
github.com/bep/gowebp v0.2.0 h1:ZVfK8i9PpZqKHEmthQSt3qCnnHycbLzBPEsVtk2ch2Q=
github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
github.com/bep/overlayfs v0.6.0/go.mod h1:NFjSmn3kCqG7KX2Lmz8qT8VhPPCwZap3UNogXawoQHM=
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE= 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/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/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/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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0=
github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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-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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8 h1:t1Ox7k2+GSzIv3fihjV7YFGb40nb/e2oyrTM/ngbzbA= 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/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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -67,19 +102,37 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 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/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/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanw/esbuild v0.15.14 h1:J/cqgL3yfj/HDHDo9txKAqyzTBYfAMuqCknkS2jhX24=
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= 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/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 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 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/getkin/kin-openapi v0.108.0 h1:EYf0GtsKa4hQNIlplGS+Au7NEfGQ1F7MoHD2kcVevPQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= 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/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= 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 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-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/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 h1:Nj29Qbkt0bZ/bJl8eccfxQp3NlU/0IW1v9eyYtQ53XQ=
github.com/gohugoio/hugo v0.106.0 h1:MDTmX2l1/zTh0HS4CADta4a/b63aiyj6yC2WW4A+UR0=
github.com/gohugoio/hugo v0.106.0/go.mod h1:eBHtMtZZrZweoC65GRsAM+jcYhHGmbzzvSccapxL4ug=
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=
github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=
github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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-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-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -123,6 +176,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 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 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.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -153,6 +207,7 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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 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/grafov/bcast v0.0.0-20190217190352-1447f067e08d/go.mod h1:RInr+B3/Tx70hYm0rpNPMTD7vH0pBG5ny/JsHAs2KcQ=
github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 h1:XWjCrg/HJRLZCbvsUxS5R/9JhwiiwNctEsRvZ1Vjz5k=
github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= 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/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.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
@ -165,12 +220,15 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/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/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 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/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 h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 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 h1:Z7pLKUb65HK6m18No8GGKT87K34NhIIEHa86rRdjxbU=
github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=
github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= 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/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.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -182,22 +240,43 @@ github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQo
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kyokomi/emoji/v2 v2.2.10 h1:1z5eMVcxFifsmEoNpdeq4UahbcicgQ4FEHuzrCVwmiI=
github.com/kyokomi/emoji/v2 v2.2.10/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 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 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/marekm4/color-extractor v1.2.0 h1:DCU/FXg3PlAwig7W5PRZshiX5x38k0aNPTxYZ6/fZb0=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= 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/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 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/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=
github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexphk8PGbo=
github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 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 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.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -208,9 +287,16 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/r3labs/sse/v2 v2.8.2 h1:YWZy2i2nLoD5fE3vLLTdTz/8wxIYIFp5XbLNmmrrNts= 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/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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
github.com/sosodev/duration v1.0.1 h1:qovz/BFb6kp30KZ4/AYZvB5Z6zANmeQja5l6W9X1w68=
github.com/sosodev/duration v1.0.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 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 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
@ -226,6 +312,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -233,6 +320,11 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=
github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ=
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -244,6 +336,9 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 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.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -252,6 +347,7 @@ 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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -274,6 +370,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 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-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/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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-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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -296,6 +393,7 @@ 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.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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/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-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-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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -329,6 +427,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -352,6 +451,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-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/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -386,8 +487,11 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/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-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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/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 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -398,6 +502,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 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/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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -452,6 +557,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/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.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -566,6 +673,7 @@ 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/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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -1,4 +1,7 @@
window.onload = function() { import ReconnectingWebSocket from 'reconnecting-websocket';
import Sortable from 'sortablejs';
window.onload = function () {
const ws = new ReconnectingWebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws"); const ws = new ReconnectingWebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws");
const items = document.querySelector("#items"); const items = document.querySelector("#items");
@ -6,6 +9,8 @@ window.onload = function() {
const submit = document.querySelector("#addplaylist") const submit = document.querySelector("#addplaylist")
const output = document.querySelector(".container2 p#output") const output = document.querySelector(".container2 p#output")
const info = document.querySelector("#info") const info = document.querySelector("#info")
const link = document.querySelector("#link")
const time = document.querySelector("#time")
const waiting = document.querySelector("#waiting") const waiting = document.querySelector("#waiting")
document.querySelector("input#next").addEventListener("click", (e) => { document.querySelector("input#next").addEventListener("click", (e) => {
@ -63,11 +68,11 @@ window.onload = function() {
}) })
ws.onopen = (e) => { ws.onopen = (e) => {
waiting.style.display = "none"; waiting.classList.add("u-hidden")
} }
ws.onclose = (e) => { ws.onclose = (e) => {
waiting.style.display = "block"; waiting.classList.remove("u-hidden")
} }
ws.onmessage = (e) => { ws.onmessage = (e) => {
@ -88,14 +93,14 @@ window.onload = function() {
el.style.removeProperty("background-color") el.style.removeProperty("background-color")
}) })
if (data.payload.pause) { if (data.payload.pause) {
info.style.display = "none" info.classList.add("u-hidden")
} else { } else {
info.style.display = "block" info.classList.remove("u-hidden")
info.children.link.children.channel.innerText = data.payload.channel link.children.channel.innerText = data.payload.channel
info.children.link.children.title.innerText = data.payload.current link.children.title.innerText = data.payload.current
info.children.link.href = "https://youtu.be/" + data.payload.song link.href = "https://youtu.be/" + data.payload.song
info.children.link.target = "_blank" link.target = "_blank"
info.children.time.innerText = `( ${msToTime(data.payload.position)} / ${msToTime(data.payload.len)} )` time.innerText = `${msToTime(data.payload.position)} / ${msToTime(data.payload.len)}`
document.querySelector(`#items > div[data-id='${data.payload.playlist}']`).style.backgroundColor = "burlywood" document.querySelector(`#items > div[data-id='${data.payload.playlist}']`).style.backgroundColor = "burlywood"
} }
setTimeout(() => { setTimeout(() => {
@ -103,15 +108,15 @@ window.onload = function() {
}, 1000); }, 1000);
break break
case "song_position": case "song_position":
info.children.time.innerText = `( ${msToTime(data.payload.position)} / ${msToTime(data.payload.len)} )` time.innerText = `${msToTime(data.payload.position)} / ${msToTime(data.payload.len)}`
info.style.display = "block" info.classList.remove("u-hidden")
break break
case "stop": case "stop":
setTimeout(() => { setTimeout(() => {
items.style.pointerEvents = 'auto' items.style.pointerEvents = 'auto'
}, 2000); }, 2000);
info.style.display = "none" info.classList.add("u-hidden")
break break
case "new_playlist": case "new_playlist":
addPlaylist(data.payload); addPlaylist(data.payload);
@ -163,7 +168,7 @@ window.onload = function() {
url.innerText = "" url.innerText = ""
}) })
document.querySelectorAll("#items").forEach(item => item.addEventListener("click", (e) => { document.querySelectorAll("#items .item").forEach(item => item.addEventListener("click", (e) => {
e.preventDefault() e.preventDefault()
e.target.parentElement.style.pointerEvents = 'none' e.target.parentElement.style.pointerEvents = 'none'
const disableui = setTimeout((t) => { const disableui = setTimeout((t) => {
@ -177,7 +182,7 @@ window.onload = function() {
})) }))
})); }));
document.querySelectorAll("#ambiance").forEach(item => item.addEventListener("click", (e) => { document.querySelectorAll("#ambiance .item").forEach(item => item.addEventListener("click", (e) => {
e.preventDefault() e.preventDefault()
e.target.parentElement.style.pointerEvents = 'none' e.target.parentElement.style.pointerEvents = 'none'
const disableui = setTimeout((t) => { const disableui = setTimeout((t) => {

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"scripts": {
"build": "esbuild js/script.js --bundle --outdir=public/"
},
"dependencies": {
"esbuild": "^0.15.14",
"reconnecting-websocket": "^4.4.0",
"sortablejs": "^1.15.0"
}
}

135
queue.go
View File

@ -1,11 +1,14 @@
package main package main
import ( import (
"container/list"
"log" "log"
"time"
"github.com/faiface/beep" "github.com/faiface/beep"
"github.com/kataras/go-events" "github.com/kataras/go-events"
"dndmusicbot/ffmpeg"
discordspeaker "dndmusicbot/speaker" discordspeaker "dndmusicbot/speaker"
) )
@ -15,68 +18,130 @@ type Ambiance struct {
} }
func init() { func init() {
app.ambiance = new(Queue) app.ambiance = beep.Mixer{}
app.ambiance.Events = app.events discordspeaker.Play(&app.ambiance)
discordspeaker.Play(app.ambiance)
app.queue = new(Queue) app.queue = new(Queue)
app.queue.list = list.New()
app.queue.Events = app.events app.queue.Events = app.events
discordspeaker.Play(app.queue) discordspeaker.Play(app.queue)
log.Println("queue.go done.") log.Println("queue.go done.")
} }
type Song struct {
Title string
Channel string
VideoID string
Length time.Duration
PCM *ffmpeg.PCM
Playlist Playlist
DLuri string
}
func (s *Song) NewStream() (err error) {
s.PCM, err = ffmpeg.NewPCM(s.DLuri, sampleRate, channels)
return
}
type Queue struct { type Queue struct {
streamers []beep.StreamSeekCloser Events events.EventEmmiter
Events events.EventEmmiter playing bool
playing bool yt VideoInfo
list *list.List
current *list.Element
} }
func (q Queue) IsPlaying() bool { func (q Queue) IsPlaying() bool {
return q.playing return q.playing
} }
func (q *Queue) Add(streamers ...beep.StreamSeekCloser) { func (q *Queue) Play() {
q.playing = true q.playing = true
q.streamers = append(q.streamers, streamers...) if q.list.Len() > 0 {
play := q.list.Front()
q.current = play
app.events.Emit("song_start")
}
}
func (q *Queue) Add(s *Song) {
el := q.list.PushBack(s)
if el == q.list.Front() {
q.Play()
}
} }
func (q Queue) QLen() int { func (q Queue) QLen() int {
return len(q.streamers) return q.list.Len()
} }
func (q Queue) Len() int { func (q Queue) Current() *Song {
if len(q.streamers) > 0 { if q.current == nil {
return q.streamers[0].Len() return new(Song)
} else {
return 0
}
}
func (q Queue) Current() beep.StreamSeekCloser {
if len(q.streamers) > 0 {
return q.streamers[0]
} else {
return nil
} }
return q.current.Value.(*Song)
} }
func (q *Queue) Reset() { func (q *Queue) Reset() {
q.playing = false q.playing = false
for _, stream := range q.streamers {
stream.Close() q.current = nil
q.list = q.list.Init()
}
func (q *Queue) Next() {
if q.current == nil {
return
} }
q.streamers = nil next := q.current.Next()
if next == nil {
next = q.list.Front()
}
pcm := next.Value.(*Song).PCM
if pcm != nil && pcm.Position() != 0 {
err := next.Value.(*Song).NewStream()
log.Println(err)
}
q.current = next
return
}
func (q *Queue) Prev() {
if q.current == nil {
return
}
prev := q.current.Prev()
if prev == nil {
prev = q.list.Back()
}
err := prev.Value.(*Song).NewStream()
if err != nil {
log.Println(err)
}
q.current = prev
return
}
func (q *Queue) Preload() {
} }
func (q *Queue) Stream(samples [][2]float64) (n int, ok bool) { func (q *Queue) Stream(samples [][2]float64) (n int, ok bool) {
if q.current != nil && !q.current.Value.(*Song).PCM.Player.Started {
q.current.Value.(*Song).PCM.Player.Start()
}
// We use the filled variable to track how many samples we've // We use the filled variable to track how many samples we've
// successfully filled already. We loop until all samples are filled. // successfully filled already. We loop until all samples are filled.
filled := 0 filled := 0
for filled < len(samples) { for filled < len(samples) {
// There are no streamers in the queue, so we stream silence. // There are no streamers in the queue, so we stream silence.
if len(q.streamers) == 0 { if q.current == nil || q.list.Len() == 0 {
for i := range samples[filled:] { for i := range samples[filled:] {
samples[i][0] = 0 samples[i][0] = 0
samples[i][1] = 0 samples[i][1] = 0
@ -85,11 +150,11 @@ func (q *Queue) Stream(samples [][2]float64) (n int, ok bool) {
} }
// We stream from the first streamer in the queue. // We stream from the first streamer in the queue.
n, ok := q.streamers[0].Stream(samples[filled:]) n, ok := q.current.Value.(*Song).PCM.Stream(samples[filled:])
// If it's drained, we pop it from the queue, thus continuing with // If it's drained, we pop it from the queue, thus continuing with
// the next streamer. // the next streamer.
if !ok { if !ok {
q.streamers = q.streamers[1:] q.Next()
q.Events.Emit("song_over", nil) q.Events.Emit("song_over", nil)
} }
// We update the number of filled samples. // We update the number of filled samples.
@ -103,9 +168,17 @@ func (q *Queue) Err() error {
} }
func (q *Queue) Position() int { func (q *Queue) Position() int {
if len(q.streamers) > 0 { if q.current == nil || q.current.Value.(*Song).PCM == nil {
return q.streamers[0].Position()
} else {
return 0 return 0
} }
return q.current.Value.(*Song).PCM.Position()
}
func (q Queue) Len() int {
if q.current == nil {
return 0
}
return int(q.current.Value.(*Song).Length.Milliseconds())
} }

View File

@ -22,7 +22,7 @@ func init() {
app.router.GET("/play/:playlist", app.Play) app.router.GET("/play/:playlist", app.Play)
app.router.GET("/reset", app.Reset) app.router.GET("/reset", app.Reset)
app.router.ServeFiles("/js/*filepath", http.Dir("js")) app.router.ServeFiles("/js/*filepath", http.Dir("public"))
app.router.ServeFiles("/css/*filepath", http.Dir("css")) app.router.ServeFiles("/css/*filepath", http.Dir("css"))
app.router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { app.router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -5,47 +5,67 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>D&D Music Bot!</title> <title>D&D Music Bot!</title>
<link rel="stylesheet" href="/css/solarized-dark.min.css"> <link rel="stylesheet" href="/css/solarized-dark.css">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
</head> </head>
<body> <body>
<div><h2 class="bot">Playlists</h2></div> <h2 class="bot">Playlists</h2>
<div id="items" class="container">
{{ range .Playlists }} <section>
<div class="item" data-id="{{ .Id }}">{{ .Title }}</div> <div id="items" class="container">
{{ end}} {{ range .Playlists }}
<div class="item locked" data-id="reset">Stop</div> <div class="item" data-id="{{ .Id }}">{{ .Title }}</div>
</div> {{ end}}
<div id="inputplaylist" class="container2"> <div class="item locked stop" data-id="reset">Stop</div>
<input name="title" type="text" placeholder="Enter name.."> </div>
<input name="url" type="text" placeholder="https://youtube.com/playlist?list=..."> </section>
<input id="addplaylist" name="submit" value="Add" type="submit">
</div> <section>
<div class="container2"> <div id="inputplaylist" class="input-container">
<input class="u-full-width" name="title" type="text" placeholder="Enter name..">
<input class="u-full-width" name="url" type="text" placeholder="https://youtube.com/playlist?list=...">
<input id="addplaylist" name="submit" value="Add" type="submit">
</div>
</section>
<section>
<p id="output"></p> <p id="output"></p>
</div>
<div id="info" class="container2"> <div id="info">
<input id="prev" name="prev" type="button" value="prev"> <a id="link" style="text-decoration: none;">
<input id="next" name="next" type="button" value="next"> <span id="channel"></span>
<a id="link" style="text-decoration: none;"> <span> - </span>
<span id="channel"></span> <span id="title"></span>
<span> - </span> </a>
<span id="title"></span>
</a> <div class="controls">
<span id="time"></span> <input id="prev" name="prev" type="button" value="prev">
</div> <span id="time"></span>
<div><h2 class="bot">Ambiance</h2></div> <input id="next" name="next" type="button" value="next">
<div id="ambiance" class="container"> </div>
{{ range .Ambiance }} </div>
<div class="item drag" data-id="{{ . }}">{{ . }}</div> </div>
{{ end}} </section>
<div class="item locked" data-id="reset">Stop</div>
</div> <h2 class="bot">Ambiance</h2>
<div id="inputambiance" class="container2">
<input name="title" type="text" placeholder="Enter name.."> <section>
<input name="url" type="text" placeholder="Enter url..."> <div id="ambiance" class="container">
<input id="addambiance" name="submit" value="Add" type="submit"> {{ range .Ambiance }}
</div> <div class="item drag" data-id="{{ . }}">{{ . }}</div>
{{ end}}
<div class="item locked stop" data-id="reset">Stop</div>
</div>
</section>
<section>
<div id="inputambiance" class="input-container">
<input class="u-full-width" name="title" type="text" placeholder="Enter name..">
<input class="u-full-width" name="url" type="text" placeholder="Enter url...">
<input id="addambiance" name="submit" value="Add" type="submit">
</div>
</section>
<!-- The Modal --> <!-- The Modal -->
<div id="waiting" class="modal"> <div id="waiting" class="modal">
<!-- Modal content --> <!-- Modal content -->
@ -53,8 +73,6 @@
<p>Waiting for bot..</p> <p>Waiting for bot..</p>
</div> </div>
</div> </div>
<script src="/js/reconnecting-websocket.min.js"></script>
<script src="/js/Sortable.min.js"></script>
<script src="/js/script.js"></script> <script src="/js/script.js"></script>
</body> </body>
</html> </html>

2
ws.go
View File

@ -70,7 +70,7 @@ func handleWS(c *websocket.Conn) error {
c.SetWriteDeadline(time.Now().Add(10 * time.Second)) c.SetWriteDeadline(time.Now().Add(10 * time.Second))
c.WriteJSON(msg) c.WriteJSON(msg)
if app.ambiance.IsPlaying() { if app.ambiance.Len() > 0 {
msg := make(map[string]interface{}) msg := make(map[string]interface{})
out := make(map[string]interface{}) out := make(map[string]interface{})
msg["event"] = "ambiance_play" msg["event"] = "ambiance_play"

151
yarn.lock Normal file
View File

@ -0,0 +1,151 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/android-arm@0.15.14":
version "0.15.14"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.14.tgz#5d0027f920eeeac313c01fd6ecb8af50c306a466"
integrity sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==
"@esbuild/linux-loong64@0.15.14":
version "0.15.14"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz#1221684955c44385f8af34f7240088b7dc08d19d"
integrity sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==
esbuild-android-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz#114e55b0d58fb7b45d7fa3d93516bd13fc8869cc"
integrity sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==
esbuild-android-arm64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz#8541f38a9aacf88e574fb13f5ad4ca51a04c12bb"
integrity sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==
esbuild-darwin-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz#b40b334db81ff1e3677a6712b23761748a157c57"
integrity sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==
esbuild-darwin-arm64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz#44b5c1477bb7bdb852dd905e906f68765e2828bc"
integrity sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==
esbuild-freebsd-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz#8c57315d238690f34b6ed0c94e5cfc04c858247a"
integrity sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==
esbuild-freebsd-arm64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz#2e92acca09258daa849e635565f52469266f0b7b"
integrity sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==
esbuild-linux-32@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz#ca5ed3e9dff82df486ddde362d7e00775a597dfd"
integrity sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==
esbuild-linux-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz#42952e1d08a299d5f573c567639fb37b033befbf"
integrity sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==
esbuild-linux-arm64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz#0c0d788099703327ec0ae70758cb2639ef6c5d88"
integrity sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==
esbuild-linux-arm@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz#751a5ca5042cd60f669b07c3bcec3dd6c4f8151c"
integrity sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==
esbuild-linux-mips64le@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz#da8ac35f2704de0b52bf53a99c12f604fbe9b916"
integrity sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==
esbuild-linux-ppc64le@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz#a315b5016917429080c3d32e03319f1ff876ac55"
integrity sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==
esbuild-linux-riscv64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz#9f2e0a935e5086d398fc19c7ff5d217bfefe3e12"
integrity sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==
esbuild-linux-s390x@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz#53108112faff5a4e1bad17f7b0b0ffa1df4b7efb"
integrity sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==
esbuild-netbsd-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz#5330efc41fe4f1c2bab5462bcfe7a4ffce7ba00a"
integrity sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==
esbuild-openbsd-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz#ee64944d863e937611fc31adf349e9bb4f5f7eac"
integrity sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==
esbuild-sunos-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz#29b0b20de6fe6ef50f9fbe533ec20dc4b595f9aa"
integrity sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==
esbuild-windows-32@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz#05e9b159d664809f7a4a8a68ed048d193457b27d"
integrity sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==
esbuild-windows-64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz#d5ae086728ab30b72969e40ed0a7a0d9082f2cdd"
integrity sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==
esbuild-windows-arm64@0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz#8eb50ab9a0ecaf058593fbad17502749306f801d"
integrity sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==
esbuild@^0.15.14:
version "0.15.14"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.14.tgz#09202b811f1710363d5088a3401a351351c79875"
integrity sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==
optionalDependencies:
"@esbuild/android-arm" "0.15.14"
"@esbuild/linux-loong64" "0.15.14"
esbuild-android-64 "0.15.14"
esbuild-android-arm64 "0.15.14"
esbuild-darwin-64 "0.15.14"
esbuild-darwin-arm64 "0.15.14"
esbuild-freebsd-64 "0.15.14"
esbuild-freebsd-arm64 "0.15.14"
esbuild-linux-32 "0.15.14"
esbuild-linux-64 "0.15.14"
esbuild-linux-arm "0.15.14"
esbuild-linux-arm64 "0.15.14"
esbuild-linux-mips64le "0.15.14"
esbuild-linux-ppc64le "0.15.14"
esbuild-linux-riscv64 "0.15.14"
esbuild-linux-s390x "0.15.14"
esbuild-netbsd-64 "0.15.14"
esbuild-openbsd-64 "0.15.14"
esbuild-sunos-64 "0.15.14"
esbuild-windows-32 "0.15.14"
esbuild-windows-64 "0.15.14"
esbuild-windows-arm64 "0.15.14"
reconnecting-websocket@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
sortablejs@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==

View File

@ -1,10 +1,12 @@
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "encoding/json"
"errors"
"io" "io"
"log" "log"
"math/big" "math/big"
@ -13,6 +15,7 @@ import (
mrand "math/rand" mrand "math/rand"
"github.com/faiface/beep" "github.com/faiface/beep"
"github.com/sosodev/duration"
"google.golang.org/api/option" "google.golang.org/api/option"
"google.golang.org/api/youtube/v3" "google.golang.org/api/youtube/v3"
) )
@ -57,7 +60,7 @@ func ShufflePlaylist(list []string) ([]string, error) {
} }
func (app *App) GetSong(list []string) string { func (app *App) GetSong(list []string) string {
return fmt.Sprintf(yt_url, list[app.plidx]) return list[app.plidx]
} }
func (app *App) GetNextSong(list []string) string { func (app *App) GetNextSong(list []string) string {
@ -65,7 +68,7 @@ func (app *App) GetNextSong(list []string) string {
if app.plidx >= len(app.active) { if app.plidx >= len(app.active) {
app.plidx = 0 app.plidx = 0
} }
return fmt.Sprintf(yt_url, list[app.plidx]) return list[app.plidx]
} }
func (app *App) GetPrevSong(list []string) string { func (app *App) GetPrevSong(list []string) string {
@ -73,7 +76,7 @@ func (app *App) GetPrevSong(list []string) string {
if app.plidx < 0 { if app.plidx < 0 {
app.plidx = len(list) app.plidx = len(list)
} }
return fmt.Sprintf(yt_url, list[app.plidx]) return list[app.plidx]
} }
func GetRandomSong(list []string) string { func GetRandomSong(list []string) string {
@ -86,7 +89,56 @@ func GetRandomSong(list []string) string {
log.Println("Failed to get random int, ", err) log.Println("Failed to get random int, ", err)
return "" return ""
} }
return fmt.Sprintf(yt_url, list[idx.Int64()]) return list[idx.Int64()]
}
type VideoInfo struct {
Title string
Channel string
Len time.Duration
Uri string
}
func (app App) Video(vid string) (out VideoInfo, err error) {
_, r, err := app.cache.GetOrCreate(vid+".videoinfo", func() (io.ReadCloser, error) {
call := app.youtube.Videos.List([]string{"snippet", "contentDetails"})
call.MaxResults(1)
call.Id(vid)
resp, err := call.Do()
if err != nil {
return nil, err
}
if len(resp.Items) != 1 {
return nil, errors.New("Response contains not 1 item!")
}
video := resp.Items[0]
songdur, err := duration.Parse(video.ContentDetails.Duration)
if err != nil {
return nil, err
}
out := new(VideoInfo)
out.Len = songdur.ToTimeDuration()
out.Title = video.Snippet.Title
out.Channel = video.Snippet.ChannelTitle
out.Uri = vid
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(out)
return io.NopCloser(&buf), nil
})
if err != nil {
return
}
json.NewDecoder(r).Decode(&out)
return out, nil
} }
func (app App) Playlist(playlist string) ([]string, error) { func (app App) Playlist(playlist string) ([]string, error) {

View File

@ -1,72 +1,31 @@
package ytdl package ytdl
import ( import (
"errors"
"fmt" "fmt"
"log"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"time"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/spf13/afero"
"github.com/tidwall/gjson"
) )
var cache *filecache.Cache
var yturl = "https://youtu.be/%s" var yturl = "https://youtu.be/%s"
func init() { func NewYTdl(vid string) ([]byte, error) {
fs := afero.NewMemMapFs() uri, err := exec.Command(
cache = filecache.NewCache(fs, 6*time.Hour, "") "./bin/yt-dlp_linux",
} fmt.Sprintf(yturl, vid),
"--cookies", "./cookies.txt",
type YTdl struct { "--no-call-home",
Title string "--no-cache-dir",
Url string "--ignore-errors",
Channel string "--newline",
Len time.Duration "--restrict-filenames",
} "-f", "140",
"--get-url",
func NewYTdl(vid string) (*YTdl, error) { ).Output()
log.Printf("Loading %s from youtube\n", vid)
_, ytdl_js, err := cache.GetOrCreateBytes(vid+".json", func() ([]byte, error) {
log.Printf("%s not found in cache, downloading info.\n", vid)
js, err := exec.Command(
"./bin/yt-dlp_linux",
fmt.Sprintf(yturl, vid),
"--cookies", "./cookies.txt",
"--no-call-home",
"--no-cache-dir",
"--ignore-errors",
"--newline",
"--restrict-filenames",
"-f", "140",
"-j",
).Output()
if err != nil {
return nil, err
}
log.Printf("%s is now cached.\n", vid)
return js, nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !gjson.ValidBytes(ytdl_js) {
return nil, errors.New("invalid json")
}
results := gjson.GetManyBytes(ytdl_js, "title", "url", "duration", "channel") return uri, nil
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 { func DownloadAmbiance(uri string, name string) error {