Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Stein Ivar Berghei | 8aa79a1e13 |
|
@ -1,16 +0,0 @@
|
|||
ambiance
|
||||
bin
|
||||
cache
|
||||
config
|
||||
cookies.txt
|
||||
dndmusicbot
|
||||
Dockerfile
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
.jsimage
|
||||
k8s
|
||||
MPD
|
||||
oauth.cfg
|
||||
test
|
||||
src
|
|
@ -16,6 +16,3 @@ ambiance
|
|||
public/
|
||||
node_modules/
|
||||
src/node_modules
|
||||
*.aac
|
||||
*.opus
|
||||
*.mp3
|
|
@ -6,4 +6,4 @@ RUN mkdir /public && chmod 777 /public
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT ["yarn"]
|
||||
ENTRYPOINT ["yarn"]
|
|
@ -1,61 +0,0 @@
|
|||
FROM golang:1.21-bookworm as builder
|
||||
|
||||
ENV FFMPEG_VERSION=5.1.2
|
||||
ENV MPD_VERSION=0.23.14
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
libopus-dev libopusfile-dev \
|
||||
meson g++ nasm \
|
||||
libfmt-dev \
|
||||
libpcre2-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libcurl4-gnutls-dev \
|
||||
libboost-dev \
|
||||
zlib1g-dev
|
||||
WORKDIR /tmp
|
||||
RUN curl -LOs http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz && tar xvf ffmpeg-${FFMPEG_VERSION}.tar.xz && \
|
||||
cd ffmpeg-${FFMPEG_VERSION} && \
|
||||
./configure \
|
||||
--enable-version3 \
|
||||
--enable-gpl \
|
||||
--enable-nonfree \
|
||||
--enable-small \
|
||||
--enable-libvorbis \
|
||||
--enable-libopus \
|
||||
--enable-postproc \
|
||||
--enable-openssl \
|
||||
--disable-debug && \
|
||||
make && \
|
||||
make install
|
||||
RUN curl -LOs https://www.musicpd.org/download/mpd/0.23/mpd-${MPD_VERSION}.tar.xz && tar xvf mpd-${MPD_VERSION}.tar.xz && \
|
||||
cd mpd-${MPD_VERSION} && \
|
||||
meson . output/release --buildtype=minsize -Db_ndebug=true && \
|
||||
ninja -C output/release
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN go build
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update && apt-get -y install \
|
||||
ca-certificates \
|
||||
libopus-dev libopusfile-dev \
|
||||
libfmt-dev \
|
||||
libpcre2-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libcurl4-gnutls-dev \
|
||||
curl && \
|
||||
curl -L https://github.com/badaix/snapcast/releases/download/v0.27.0/snapclient_0.27.0-1_without-pulse_amd64.deb -o /tmp/snapcast.deb && \
|
||||
curl -L http://ftp.no.debian.org/debian/pool/main/f/flac/libflac8_1.3.3-2+deb11u2_amd64.deb -o /tmp/libflac8.deb && \
|
||||
apt -y install /tmp/snapcast.deb /tmp/libflac8.deb && rm -rf /tmp/*.deb
|
||||
COPY --from=builder /src/dndmusicbot /app/
|
||||
COPY --from=builder /tmp/mpd-0.23.14/output/release/mpd /usr/local/bin
|
||||
COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin
|
||||
COPY mp3 /app/mp3
|
||||
ADD tmpl /app/tmpl
|
||||
WORKDIR /app
|
||||
ENTRYPOINT [ "/app/dndmusicbot" ]
|
|
@ -1,16 +0,0 @@
|
|||
ambiance
|
||||
bin
|
||||
cache
|
||||
config
|
||||
cookies.txt
|
||||
dndmusicbot
|
||||
Dockerfile
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
.jsimage
|
||||
k8s
|
||||
MPD
|
||||
oauth.cfg
|
||||
test
|
||||
src
|
|
@ -1,2 +0,0 @@
|
|||
ambiance
|
||||
*.mp3
|
5
Makefile
5
Makefile
|
@ -10,9 +10,8 @@ js: image public/script.js
|
|||
|
||||
image: .jsimage
|
||||
|
||||
.jsimage: export DOCKER_BUILDKIT=1
|
||||
.jsimage: Dockerfile.js
|
||||
@docker build -t dndmusicbot-js-build . -f Dockerfile.js
|
||||
.jsimage: Dockerfile
|
||||
@docker build -t dndmusicbot-js-build .
|
||||
@touch .jsimage
|
||||
|
||||
src/node_modules:
|
||||
|
|
87
ambiance.go
87
ambiance.go
|
@ -5,19 +5,17 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davecheney/xattr"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
//var httpClient = new(http.Client)
|
||||
var httpClient = new(http.Client)
|
||||
|
||||
type Ambiance struct {
|
||||
Id string
|
||||
|
@ -26,7 +24,7 @@ type Ambiance struct {
|
|||
}
|
||||
|
||||
func GetAmbiance(id string) (amb Ambiance, err error) {
|
||||
fp := filepath.Join(config.GetString("ambiance.path"), id+".opus")
|
||||
fp := filepath.Join("./ambiance", id+".opus")
|
||||
_, err = os.Stat(fp)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -45,13 +43,13 @@ func GetAmbiance(id string) (amb Ambiance, err error) {
|
|||
}
|
||||
|
||||
func GetAmbiances() (amb []Ambiance, err error) {
|
||||
files, err := os.ReadDir(config.GetString("ambiance.path"))
|
||||
files, err := os.ReadDir("./ambiance")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
title, err := xattr.Getxattr(filepath.Join(config.GetString("ambiance.path"), file.Name()), "title")
|
||||
title, err := xattr.Getxattr(filepath.Join("./ambiance", file.Name()), "title")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
|
@ -59,7 +57,7 @@ func GetAmbiances() (amb []Ambiance, err error) {
|
|||
amb = append(amb, Ambiance{
|
||||
Id: file.Name()[:len(file.Name())-len(filepath.Ext(file.Name()))],
|
||||
Title: string(title),
|
||||
Path: filepath.Join(config.GetString("ambiance.path"), file.Name()),
|
||||
Path: filepath.Join("./ambiance", file.Name()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -69,19 +67,6 @@ func GetAmbiances() (amb []Ambiance, err error) {
|
|||
func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||
var amb Ambiance
|
||||
|
||||
ev := Event{"ambiance_add_start", map[string]string{
|
||||
"name": title,
|
||||
}}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
|
||||
defer func() {
|
||||
ev = Event{"ambiance_add_finish", map[string]string{
|
||||
"name": title,
|
||||
}}
|
||||
go ws.SendEvent(ev)
|
||||
}()
|
||||
|
||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.opus").Output()
|
||||
if err != nil {
|
||||
return amb, err
|
||||
|
@ -89,28 +74,33 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
|||
|
||||
tmpfile = tmpfile[:len(tmpfile)-1]
|
||||
|
||||
log.Printf("Parsing vid from %s", uri)
|
||||
vid, err := YTUrl(uri)
|
||||
if err != nil {
|
||||
return amb, err
|
||||
}
|
||||
|
||||
vinfo, err := app.youtube.GetVideoFromID(vid)
|
||||
log.Printf("Start YTdl for %s", uri)
|
||||
dluri, err := NewYTdl(vid)
|
||||
if err != nil {
|
||||
return amb, err
|
||||
}
|
||||
|
||||
log.Printf("Start ffmpeg for %s (%s)", vid, vinfo.VideoDetails.Title)
|
||||
log.Printf("Start download.. %s", uri)
|
||||
resp, err := httpClient.Get(string(dluri))
|
||||
if err != nil {
|
||||
return amb, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ff := exec.Command(
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i", vinfo.GetHLSPlaylist("234"),
|
||||
"-i", "-",
|
||||
"-vn",
|
||||
//"-acodec", "copy",
|
||||
"-acodec", "copy",
|
||||
"-movflags", "+faststart",
|
||||
"-t", "01:00:00",
|
||||
"-ar", "48000",
|
||||
"-v", "quiet",
|
||||
"-v", "error",
|
||||
// "-stats",
|
||||
"-progress", "pipe:1",
|
||||
// "-af", "loudnorm=I=-16:LRA=11:TP=-1.5",
|
||||
|
@ -123,6 +113,7 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
|||
}
|
||||
|
||||
ff.Stderr = os.Stderr
|
||||
ff.Stdin = resp.Body
|
||||
|
||||
err = ff.Start()
|
||||
if err != nil {
|
||||
|
@ -130,15 +121,17 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
|||
}
|
||||
|
||||
log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile))
|
||||
|
||||
ev = Event{"ambiance_encode_start", map[string]string{
|
||||
"name": title,
|
||||
}}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_encode_start"
|
||||
data := make(map[string]string)
|
||||
data["name"] = title
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
|
||||
msg = make(map[string]interface{})
|
||||
msg["event"] = "ambiance_encode_progress"
|
||||
data = make(map[string]string)
|
||||
data["name"] = title
|
||||
|
||||
scanner := bufio.NewScanner(ffprogress)
|
||||
|
||||
|
@ -149,17 +142,10 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
|||
}
|
||||
|
||||
prate.Do(func() {
|
||||
out_time, ok := data["out_time_ms"]
|
||||
if ok {
|
||||
out_time_ms, _ := strconv.Atoi(out_time)
|
||||
percent := fmt.Sprintf("%.0f", math.Floor((float64(out_time_ms)/float64(time.Hour.Microseconds()))*100))
|
||||
data["percent"] = percent
|
||||
}
|
||||
|
||||
go ws.SendEvent(Event{"ambiance_encode_progress", data})
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
})
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return amb, err
|
||||
}
|
||||
|
@ -169,14 +155,15 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
|||
return amb, err
|
||||
}
|
||||
|
||||
ev = Event{"ambiance_encode_complete", map[string]string{
|
||||
"name": title,
|
||||
}}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
msg = make(map[string]interface{})
|
||||
msg["event"] = "ambiance_encode_complete"
|
||||
data = make(map[string]string)
|
||||
data["name"] = title
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
|
||||
id := uuid.New()
|
||||
fn := filepath.Join(config.GetString("ambiance.path"), fmt.Sprintf("%s.aac", id.String()))
|
||||
fn := filepath.Join("./ambiance", fmt.Sprintf("%s.opus", id.String()))
|
||||
|
||||
log.Printf("Moving to %s", fn)
|
||||
|
||||
|
|
61
bot.go
61
bot.go
|
@ -5,23 +5,13 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"dndmusicbot/youtube"
|
||||
|
||||
"github.com/diamondburned/arikawa/v3/state"
|
||||
"github.com/diamondburned/arikawa/v3/voice"
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/steino/gompd/v2/mpd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -32,8 +22,9 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
app = new(App)
|
||||
config = viper.GetViper()
|
||||
bfs = afero.NewBasePathFs(afero.NewOsFs(), "cache")
|
||||
cache = filecache.NewCache(bfs, 1*time.Hour, "")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -48,43 +39,12 @@ func init() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
app.plmutex = &sync.Mutex{}
|
||||
mpd_mutex = &sync.Mutex{}
|
||||
|
||||
log.Println("bot.go done.")
|
||||
}
|
||||
|
||||
type App struct {
|
||||
discord *state.State
|
||||
voice *voice.Session
|
||||
youtube *youtube.Client
|
||||
ambiance beep.Mixer
|
||||
curamb Ambiance
|
||||
events events.EventEmmiter
|
||||
db *pgx.Conn
|
||||
router *httprouter.Router
|
||||
cache *filecache.Cache
|
||||
mpdc context.CancelFunc
|
||||
mpdw *mpd.Watcher
|
||||
mpd *mpd.Client
|
||||
plmutex *sync.Mutex
|
||||
plcancel context.CancelFunc
|
||||
}
|
||||
|
||||
var cwd string
|
||||
|
||||
func init() {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cwd = filepath.Dir(ex)
|
||||
}
|
||||
|
||||
func main() {
|
||||
bfs := afero.NewBasePathFs(afero.NewOsFs(), "cache")
|
||||
app.cache = filecache.NewCache(bfs, 1*time.Hour, "")
|
||||
|
||||
prune := time.NewTicker(15 * time.Minute)
|
||||
ticker := time.NewTicker(300 * time.Millisecond)
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
|
@ -93,18 +53,15 @@ func main() {
|
|||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
app.db.Close(ctx)
|
||||
app.mpdw.Close()
|
||||
app.mpdc()
|
||||
app.voice.Leave(ctx)
|
||||
app.cache.Prune(false)
|
||||
db.Close(ctx)
|
||||
mpdw.Close()
|
||||
mpdcf()
|
||||
dvoice.Leave(ctx)
|
||||
dgvc()
|
||||
app.discord.Close()
|
||||
dstate.Close()
|
||||
return
|
||||
case <-ticker.C:
|
||||
app.events.Emit("tick")
|
||||
case <-prune.C:
|
||||
app.cache.Prune(false)
|
||||
ev.Emit("tick")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
db.go
16
db.go
|
@ -8,11 +8,13 @@ import (
|
|||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
var db *pgx.Conn
|
||||
|
||||
func init() {
|
||||
log.Println("db.go loading..")
|
||||
var err error
|
||||
|
||||
app.db, err = pgx.Connect(context.Background(), config.GetString("db.connstring"))
|
||||
db, err = pgx.Connect(context.Background(), config.GetString("db.connstring"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -25,8 +27,8 @@ type Playlist struct {
|
|||
Title string
|
||||
}
|
||||
|
||||
func (app App) GetPlaylists() (playlists []Playlist, err error) {
|
||||
rows, err := app.db.Query(context.Background(), "SELECT id, url, title FROM playlists")
|
||||
func GetPlaylists() (playlists []Playlist, err error) {
|
||||
rows, err := db.Query(context.Background(), "SELECT id, url, title FROM playlists")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -35,8 +37,8 @@ func (app App) GetPlaylists() (playlists []Playlist, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (app App) GetPlaylist(id uuid.UUID) (*Playlist, error) {
|
||||
rows, err := app.db.Query(context.Background(), "SELECT id, url, title FROM playlists where id=$1 limit 1", id)
|
||||
func GetPlaylist(id uuid.UUID) (*Playlist, error) {
|
||||
rows, err := db.Query(context.Background(), "SELECT id, url, title FROM playlists where id=$1 limit 1", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -50,9 +52,9 @@ func (app App) GetPlaylist(id uuid.UUID) (*Playlist, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (app App) AddPlaylist(title string, uri string) (uuid.UUID, error) {
|
||||
func AddPlaylist(title string, uri string) (uuid.UUID, error) {
|
||||
id := uuid.New()
|
||||
_, err := app.db.Exec(context.Background(), "INSERT INTO playlists VALUES ($1, $2, $3)", id, title, uri)
|
||||
_, err := db.Exec(context.Background(), "INSERT INTO playlists VALUES ($1, $2, $3)", id, title, uri)
|
||||
if err != nil {
|
||||
return *new(uuid.UUID), err
|
||||
}
|
||||
|
|
12
discord.go
12
discord.go
|
@ -11,7 +11,11 @@ import (
|
|||
"github.com/diamondburned/arikawa/v3/voice/voicegateway"
|
||||
)
|
||||
|
||||
var dgvc context.CancelFunc
|
||||
var (
|
||||
dstate *state.State
|
||||
dvoice *voice.Session
|
||||
dgvc context.CancelFunc
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Println("discord.go loading..")
|
||||
|
@ -47,10 +51,10 @@ func init() {
|
|||
|
||||
v.Speaking(ctx, voicegateway.NotSpeaking)
|
||||
|
||||
app.discord = s
|
||||
app.voice = v
|
||||
dstate = s
|
||||
dvoice = v
|
||||
|
||||
discordspeaker.Init(v, config.GetInt("discord.bitrate"))
|
||||
discordspeaker.Init(v)
|
||||
|
||||
log.Println("discord.go done.")
|
||||
}
|
||||
|
|
356
events.go
356
events.go
|
@ -2,21 +2,21 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"dndmusicbot/opus"
|
||||
discordspeaker "dndmusicbot/speaker"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/effects"
|
||||
"github.com/fhs/gompd/v2/mpd"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/steino/gompd/v2/mpd"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
ytdl "github.com/kkdai/youtube/v2"
|
||||
)
|
||||
|
||||
type SongInfo struct {
|
||||
|
@ -30,41 +30,46 @@ type SongInfo struct {
|
|||
Song string `json:"song,omitempty"`
|
||||
}
|
||||
|
||||
var l = rate.Sometimes{Interval: 800 * time.Millisecond}
|
||||
var ytdl_client = ytdl.Client{}
|
||||
var (
|
||||
l = rate.Sometimes{Interval: 800 * time.Millisecond}
|
||||
)
|
||||
|
||||
var (
|
||||
ev events.EventEmmiter
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Println("events.go loading...")
|
||||
|
||||
app.events = events.New()
|
||||
ev = events.New()
|
||||
|
||||
app.events.On("load_playlist", app.loadPlaylist)
|
||||
app.events.On("add_playlist", app.addPlaylist)
|
||||
ev.On("load_playlist", loadPlaylist)
|
||||
ev.On("add_playlist", addPlaylist)
|
||||
|
||||
//app.events.On("preload_song", app.preloadSong)
|
||||
//app.events.On("song_over", app.songInfo)
|
||||
//app.events.On("song_start", app.songInfo)
|
||||
app.events.On("player", app.songInfo)
|
||||
//app.events.On("song_position", app.songPosition)
|
||||
//ev.On("preload_song", app.preloadSong)
|
||||
//ev.On("song_over", app.songInfo)
|
||||
//ev.On("song_start", app.songInfo)
|
||||
ev.On("player", songInfo)
|
||||
//ev.On("song_position", app.songPosition)
|
||||
|
||||
app.events.On("ambiance_play", app.ambiancePlay)
|
||||
app.events.On("ambiance_stop", app.ambianceStop)
|
||||
app.events.On("ambiance_add", app.ambianceAdd)
|
||||
ev.On("ambiance_play", ambiancePlay)
|
||||
ev.On("ambiance_stop", ambianceStop)
|
||||
ev.On("ambiance_add", ambianceAdd)
|
||||
|
||||
app.events.On("stop", app.stop)
|
||||
app.events.On("next", app.nextSong)
|
||||
app.events.On("prev", app.prevSong)
|
||||
ev.On("stop", stop)
|
||||
ev.On("next", nextSong)
|
||||
ev.On("prev", prevSong)
|
||||
|
||||
app.events.On("vol_up", app.volup)
|
||||
app.events.On("vol_down", app.voldown)
|
||||
app.events.On("vol_set", app.volset)
|
||||
ev.On("vol_up", volup)
|
||||
ev.On("vol_down", voldown)
|
||||
ev.On("vol_set", volset)
|
||||
|
||||
//app.events.On("tick", app.checkQueue)
|
||||
app.events.On("tick", app.songPosition)
|
||||
//app.events.On("tick", app.checkTimeleft)
|
||||
//ev.On("tick", app.checkQueue)
|
||||
ev.On("tick", songPosition)
|
||||
//ev.On("tick", app.checkTimeleft)
|
||||
}
|
||||
|
||||
func (app *App) volup(payload ...interface{}) {
|
||||
func volup(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("volup called without a payload.")
|
||||
return
|
||||
|
@ -92,7 +97,7 @@ func (app *App) volup(payload ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (app *App) voldown(payload ...interface{}) {
|
||||
func voldown(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("voldown called without a payload.")
|
||||
return
|
||||
|
@ -120,7 +125,7 @@ func (app *App) voldown(payload ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (app *App) volset(payload ...interface{}) {
|
||||
func volset(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("volset called without a payload.")
|
||||
return
|
||||
|
@ -148,70 +153,74 @@ func (app *App) volset(payload ...interface{}) {
|
|||
amb_volume.Volume = vol
|
||||
}
|
||||
|
||||
ev := Event{"volume", map[string]float64{
|
||||
"playlist": pl_volume.Volume,
|
||||
"ambiance": amb_volume.Volume,
|
||||
}}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
sendVolume()
|
||||
}
|
||||
|
||||
func (app *App) songInfoEvent(event string) (ev Event, err error) {
|
||||
ev.Event = event
|
||||
func sendVolume() {
|
||||
msg := make(map[string]interface{})
|
||||
out := make(map[string]float64)
|
||||
msg["event"] = "volume"
|
||||
out["playlist"] = pl_volume.Volume
|
||||
out["ambiance"] = amb_volume.Volume
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
||||
status, err := app.mpd.Status()
|
||||
func songInfoEvent(event string) map[string]interface{} {
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = event
|
||||
status, err := mpdc.Status()
|
||||
if err != nil {
|
||||
return
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
cur, err := app.mpd.CurrentSong()
|
||||
cur, err := mpdc.CurrentSong()
|
||||
if err != nil {
|
||||
return
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
info := new(SongInfo)
|
||||
|
||||
if status["state"] != "play" {
|
||||
info.Pause = true
|
||||
ev.Payload = info
|
||||
return
|
||||
msg["payload"] = info
|
||||
return msg
|
||||
}
|
||||
|
||||
duration, ok := status["duration"]
|
||||
if ok && duration != "" {
|
||||
var slen float64
|
||||
slen, err = strconv.ParseFloat(duration, 64)
|
||||
slen, err := strconv.ParseFloat(duration, 64)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
info.Length = time.Duration(slen * float64(time.Second)).Milliseconds()
|
||||
}
|
||||
|
||||
elapsed, ok := status["elapsed"]
|
||||
if ok && elapsed != "" {
|
||||
var spos float64
|
||||
spos, err = strconv.ParseFloat(elapsed, 64)
|
||||
spos, err := strconv.ParseFloat(elapsed, 64)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
info.Position = time.Duration(spos * float64(time.Second)).Milliseconds()
|
||||
}
|
||||
|
||||
album, ok := cur["Album"]
|
||||
if ok {
|
||||
var plid uuid.UUID
|
||||
plid, err = uuid.ParseBytes([]byte(album))
|
||||
plid, err := uuid.ParseBytes([]byte(album))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
var pl *Playlist
|
||||
pl, err = app.GetPlaylist(plid)
|
||||
|
||||
pl, err := GetPlaylist(plid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
info.Playlist = pl.Id
|
||||
|
@ -233,12 +242,12 @@ func (app *App) songInfoEvent(event string) (ev Event, err error) {
|
|||
info.Song = location
|
||||
}
|
||||
|
||||
ev.Payload = *info
|
||||
msg["payload"] = *info
|
||||
|
||||
return ev, nil
|
||||
return msg
|
||||
}
|
||||
|
||||
func (app *App) ambiancePlay(payload ...interface{}) {
|
||||
func ambiancePlay(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("ambiance_play called without a payload.")
|
||||
return
|
||||
|
@ -264,59 +273,56 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
err = app.mpd.Partition("ambiance")
|
||||
f, err := os.Open(amb.Path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(filepath.Join(cwd, amb.Path))
|
||||
err = app.mpd.Add(filepath.Join(cwd, amb.Path))
|
||||
play, err := opus.New(f)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = app.mpd.Play(0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
loop := beep.Loop(-1, play)
|
||||
|
||||
volume := &effects.Volume{
|
||||
Streamer: loop,
|
||||
Base: 2,
|
||||
Volume: -2.5,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
err = app.mpd.Partition("default")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
discordspeaker.Lock()
|
||||
amb_mixer.Clear()
|
||||
amb_mixer.Add(volume)
|
||||
discordspeaker.Unlock()
|
||||
|
||||
app.curamb = amb
|
||||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
|
||||
ev := Event{"ambiance_play", map[string]string{
|
||||
"id": id,
|
||||
}}
|
||||
amb_curr = amb
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
msg["event"] = "ambiance_play"
|
||||
out["id"] = id
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
||||
func (app *App) ambianceStop(payload ...interface{}) {
|
||||
func ambianceStop(payload ...interface{}) {
|
||||
log.Println("Stopping ambiance")
|
||||
discordspeaker.Lock()
|
||||
amb_mixer.Clear()
|
||||
discordspeaker.Unlock()
|
||||
|
||||
err := app.mpd.Partition("ambiance")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_stop"
|
||||
ws_msg <- msg
|
||||
|
||||
app.mpd.Stop()
|
||||
app.mpd.Clear()
|
||||
|
||||
err = app.mpd.Partition("default")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go ws.SendEvent(Event{"ambiance_stop", nil})
|
||||
}
|
||||
|
||||
func (app *App) ambianceAdd(payload ...interface{}) {
|
||||
func ambianceAdd(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("addPlaylist called without a payload.")
|
||||
return
|
||||
|
@ -350,16 +356,17 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
ev := Event{"ambiance_add", map[string]string{
|
||||
"title": amb.Title,
|
||||
"id": amb.Id,
|
||||
}}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_add"
|
||||
out["title"] = amb.Title
|
||||
out["id"] = amb.Id
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
||||
func (app *App) songPosition(payload ...interface{}) {
|
||||
status, err := app.mpd.Status()
|
||||
func songPosition(payload ...interface{}) {
|
||||
status, err := mpdc.Status()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
@ -370,66 +377,77 @@ func (app *App) songPosition(payload ...interface{}) {
|
|||
}
|
||||
|
||||
l.Do(func() {
|
||||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
|
||||
slen, _ := strconv.ParseFloat(status["duration"], 64)
|
||||
spos, _ := strconv.ParseFloat(status["elapsed"], 64)
|
||||
slen, err := strconv.ParseFloat(status["duration"], 64)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
ev := Event{"song_position", map[string]int64{
|
||||
"len": time.Duration(slen * float64(time.Second)).Milliseconds(),
|
||||
"position": time.Duration(spos * float64(time.Second)).Milliseconds(),
|
||||
}}
|
||||
spos, err := strconv.ParseFloat(status["elapsed"], 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
msg["event"] = "song_position"
|
||||
out["len"] = time.Duration(slen * float64(time.Second)).Milliseconds()
|
||||
out["position"] = time.Duration(spos * float64(time.Second)).Milliseconds()
|
||||
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (app *App) songInfo(payload ...interface{}) {
|
||||
func songInfo(payload ...interface{}) {
|
||||
log.Println("song_info event received")
|
||||
|
||||
ev, err := app.songInfoEvent("song_info")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
msg := songInfoEvent("song_info")
|
||||
if msg != nil {
|
||||
ws_msg <- msg
|
||||
}
|
||||
go ws.SendEvent(ev)
|
||||
}
|
||||
|
||||
func (app *App) stop(payload ...interface{}) {
|
||||
func stop(payload ...interface{}) {
|
||||
log.Println("stop event received")
|
||||
|
||||
app.plmutex.Lock()
|
||||
if app.plcancel != nil {
|
||||
app.plcancel()
|
||||
mpd_mutex.Lock()
|
||||
if mpd_plcf != nil {
|
||||
mpd_plcf()
|
||||
}
|
||||
app.mpd.Stop()
|
||||
app.plmutex.Unlock()
|
||||
mpdc.Stop()
|
||||
mpd_mutex.Unlock()
|
||||
|
||||
go ws.SendEvent(Event{"stop", nil})
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = "stop"
|
||||
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
||||
func (app *App) prevSong(payload ...interface{}) {
|
||||
func prevSong(payload ...interface{}) {
|
||||
log.Println("prev_song event received")
|
||||
app.plmutex.Lock()
|
||||
err := app.mpd.Previous()
|
||||
app.plmutex.Unlock()
|
||||
mpd_mutex.Lock()
|
||||
err := mpdc.Previous()
|
||||
mpd_mutex.Unlock()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) nextSong(payload ...interface{}) {
|
||||
func nextSong(payload ...interface{}) {
|
||||
log.Println("next_song event received")
|
||||
|
||||
app.plmutex.Lock()
|
||||
err := app.mpd.Next()
|
||||
app.plmutex.Unlock()
|
||||
mpd_mutex.Lock()
|
||||
err := mpdc.Next()
|
||||
mpd_mutex.Unlock()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) addPlaylist(payload ...interface{}) {
|
||||
func addPlaylist(payload ...interface{}) {
|
||||
if !(len(payload) > 0) {
|
||||
log.Println("addPlaylist called without a payload.")
|
||||
return
|
||||
|
@ -469,25 +487,25 @@ func (app *App) addPlaylist(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = ytdl_client.GetPlaylist(plurl)
|
||||
_, err = YTPlaylist(plid)
|
||||
if err != nil {
|
||||
log.Println("Error getting youtube playlist info,", plid)
|
||||
}
|
||||
|
||||
id, err := app.AddPlaylist(pltitle, plid)
|
||||
id, err := AddPlaylist(pltitle, plid)
|
||||
if err != nil {
|
||||
log.Println("Error getting youtube playlist info,", plid)
|
||||
}
|
||||
|
||||
ev := Event{"new_playlist", map[string]string{
|
||||
"url": id.String(),
|
||||
"title": pltitle,
|
||||
}}
|
||||
msg := make(map[string]interface{})
|
||||
|
||||
go ws.SendEvent(ev)
|
||||
msg["event"] = "new_playlist"
|
||||
msg["payload"] = map[string]string{"url": id.String(), "title": pltitle}
|
||||
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
||||
func (app *App) loadPlaylist(payload ...interface{}) {
|
||||
func loadPlaylist(payload ...interface{}) {
|
||||
log.Println("load_playlist event received")
|
||||
|
||||
if !(len(payload) > 0) {
|
||||
|
@ -514,42 +532,62 @@ func (app *App) loadPlaylist(payload ...interface{}) {
|
|||
}
|
||||
|
||||
log.Println("Loading new playlist: ", id)
|
||||
pl, err := app.GetPlaylist(id)
|
||||
pl, err := GetPlaylist(id)
|
||||
if err != nil {
|
||||
log.Println("Unable to find playlist with id,", id)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := ytdl_client.GetPlaylist(pl.Url)
|
||||
list, err := YTPlaylist(pl.Url)
|
||||
if err != nil {
|
||||
log.Println("Error getting playlist info,", id)
|
||||
return
|
||||
}
|
||||
|
||||
rand.Shuffle(len(list.Videos), func(i, j int) { list.Videos[i], list.Videos[j] = list.Videos[j], list.Videos[i] })
|
||||
|
||||
app.plmutex.Lock()
|
||||
if app.plcancel != nil {
|
||||
app.plcancel()
|
||||
list, err = ShufflePlaylist(list)
|
||||
if err != nil {
|
||||
log.Println("Unable to shuffle playlist")
|
||||
return
|
||||
}
|
||||
|
||||
app.mpd.Stop()
|
||||
app.mpd.Clear()
|
||||
app.plmutex.Unlock()
|
||||
mpd_mutex.Lock()
|
||||
if mpd_plcf != nil {
|
||||
mpd_plcf()
|
||||
}
|
||||
mpdc.Stop()
|
||||
mpdc.Clear()
|
||||
mpd_mutex.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
app.plcancel = cancel
|
||||
mpd_plcf = cancel
|
||||
|
||||
go func() {
|
||||
defer cancel()
|
||||
|
||||
for _, ytinfo := range list.Videos {
|
||||
log.Printf("Adding %s (%s - %s)", ytinfo.ID, ytinfo.Author, ytinfo.Title)
|
||||
for _, vid := range list {
|
||||
log.Printf("Adding %s", vid)
|
||||
|
||||
ytinfo, err := YTVideo(vid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, yt, err := cache.GetOrCreateBytes(vid+".txt", func() ([]byte, error) {
|
||||
uri, err := NewYTdl(vid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uri, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Run as a local function so we can defer the mutex unlock incase one of these errors.
|
||||
ok := func() (ok bool) {
|
||||
app.plmutex.Lock()
|
||||
defer app.plmutex.Unlock()
|
||||
mpd_mutex.Lock()
|
||||
defer mpd_mutex.Unlock()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return false
|
||||
|
@ -558,43 +596,41 @@ func (app *App) loadPlaylist(payload ...interface{}) {
|
|||
ok = true
|
||||
|
||||
// state:stop
|
||||
songid, err := app.mpd.AddID("http://localhost:"+config.GetString("web.port")+"/youtube/"+ytinfo.ID, 0)
|
||||
songid, err := mpdc.AddID(string(yt), 0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mpdcmd := app.mpd.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("artist"), ytinfo.Author)
|
||||
mpdcmd := mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("artist"), ytinfo.Channel)
|
||||
err = mpdcmd.OK()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mpdcmd = app.mpd.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("title"), ytinfo.Title)
|
||||
mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("title"), ytinfo.Title)
|
||||
err = mpdcmd.OK()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mpdcmd = app.mpd.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("location"), ytinfo.ID)
|
||||
mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("location"), vid)
|
||||
err = mpdcmd.OK()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mpdcmd = app.mpd.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("album"), pl.Id.String())
|
||||
mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("album"), pl.Id.String())
|
||||
err = mpdcmd.OK()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
app.mpd.Play(-1)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
mpdc.Play(-1)
|
||||
|
||||
return
|
||||
}()
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/faiface/beep"
|
||||
)
|
||||
|
||||
type PCM struct {
|
||||
|
|
79
go.mod
79
go.mod
|
@ -1,40 +1,36 @@
|
|||
module dndmusicbot
|
||||
|
||||
go 1.21
|
||||
replace github.com/fhs/gompd/v2 => /home/steino/dev/go/gompd/
|
||||
|
||||
toolchain go1.21.3
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b
|
||||
github.com/diamondburned/arikawa/v3 v3.2.1-0.20230320210521-82c55dffac71
|
||||
github.com/diamondburned/arikawa/v3 v3.1.1-0.20221210140357-3b98cde06f9e
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/fhs/gompd/v2 v2.3.0
|
||||
github.com/gohugoio/hugo v0.106.0
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gopxl/beep v1.1.0
|
||||
github.com/gorilla/sessions v1.1.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d
|
||||
github.com/grafov/m3u8 v0.12.0
|
||||
github.com/jackc/pgx/v5 v5.1.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kataras/go-events v0.0.3
|
||||
github.com/kkdai/youtube/v2 v2.9.0
|
||||
github.com/markbates/goth v1.76.1
|
||||
github.com/peterhellberg/link v1.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/romantomjak/shoutcast v1.2.0
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/steino/gompd/v2 v2.3.1
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||
golang.org/x/net v0.15.0
|
||||
github.com/sosodev/duration v1.0.1
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
golang.org/x/net v0.3.0
|
||||
golang.org/x/time v0.3.0
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302
|
||||
google.golang.org/api v0.103.0
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.12.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
|
||||
|
@ -47,51 +43,48 @@ require (
|
|||
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.10.0 // indirect
|
||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e // indirect
|
||||
github.com/fhs/gompd/v2 v2.3.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // 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/protobuf v1.5.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||
github.com/gorilla/schema v1.2.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/icza/bitio v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // 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.7 // 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.15 // indirect
|
||||
github.com/mewkiz/flac v1.0.9 // indirect
|
||||
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 // 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/niklasfasching/go-org v1.6.5 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // 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/pretty v1.2.1 // indirect
|
||||
github.com/yuin/goldmark v1.5.3 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/fatih/set.v0 v0.2.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
260
go.sum
260
go.sum
|
@ -14,18 +14,23 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
|||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
|
||||
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
|
||||
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
|
||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
|
@ -42,8 +47,6 @@ github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZd
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d h1:2Fn0vK/lH/pI2/TtUTFgb3iKkbw7TDlxBLglELToDRo=
|
||||
github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d/go.mod h1:jmxJwIPbDUrofy2H3d28lYzz+RL9qt8GltDAAJ/mlb0=
|
||||
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=
|
||||
|
@ -51,7 +54,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
|||
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/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
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=
|
||||
|
@ -59,30 +61,21 @@ github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlw
|
|||
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/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY=
|
||||
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
||||
github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=
|
||||
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/gowebp v0.2.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||
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/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
||||
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
|
||||
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
|
||||
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
|
||||
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
|
||||
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.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/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=
|
||||
|
@ -98,21 +91,11 @@ 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/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b h1:a/CjIrvEH2NkUUIo4sqWIw+h3E63ttmS8L8Vx3ZaLS0=
|
||||
github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b/go.mod h1:Gc/R1HBRJIEElnD4PGXGQZQYMb14oPbvTovm6WeuAvk=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/diamondburned/arikawa/v3 v3.2.1-0.20230320210521-82c55dffac71 h1:1Wqec9jTprYuWfx/XiJVaRkH5RCkyrgnHdFj0O5NFn4=
|
||||
github.com/diamondburned/arikawa/v3 v3.2.1-0.20230320210521-82c55dffac71/go.mod h1:+ifmDonP/JdBiUOzZmVReEjPTHDUSkyqqRRmjSf9NE8=
|
||||
github.com/diamondburned/arikawa/v3 v3.1.1-0.20221210140357-3b98cde06f9e h1:vP/3xB9FOXh+rj86fQdlz/LFBmSeGmFHnmpDbF7QTCw=
|
||||
github.com/diamondburned/arikawa/v3 v3.1.1-0.20221210140357-3b98cde06f9e/go.mod h1:5jBSNnp82Z/EhsKa6Wk9FsOqSxfVkNZDTDBPOj47LpY=
|
||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e h1:UvQD6hTSfeM6hhTQ24Dlw2RppP05W7SWbWb6kubJAog=
|
||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
@ -120,55 +103,42 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
|
|||
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/evanw/esbuild v0.15.14 h1:J/cqgL3yfj/HDHDo9txKAqyzTBYfAMuqCknkS2jhX24=
|
||||
github.com/evanw/esbuild v0.15.14/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/fhs/gompd/v2 v2.3.0 h1:wuruUjmOODRlJhrYx73rJnzS7vTSXSU7pWmZtM3VPE0=
|
||||
github.com/fhs/gompd/v2 v2.3.0/go.mod h1:nNdZtcpD5VpmzZbRl5rV6RhxeMmAWTxEsSIMBkmMIy4=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/frankban/quicktest v1.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.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/getkin/kin-openapi v0.108.0 h1:EYf0GtsKa4hQNIlplGS+Au7NEfGQ1F7MoHD2kcVevPQ=
|
||||
github.com/getkin/kin-openapi v0.108.0/go.mod h1:QtwUNt0PAAgIIBEvFWYfB7dfngxtAaqCX1zYHMZDeK8=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk=
|
||||
github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE=
|
||||
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/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 h1:Nj29Qbkt0bZ/bJl8eccfxQp3NlU/0IW1v9eyYtQ53XQ=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
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-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2 h1:hXPcSazn8wKOfSb9y2m1bdgUMlDxVDarxh3lJVbC6JE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
|
@ -191,8 +161,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
|||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -203,11 +173,11 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -218,42 +188,28 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb h1:LCMfzVg3sflxTs4UvuP4D8CkoZnfHLe2qzqgDn/4OHs=
|
||||
github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gopxl/beep v1.1.0 h1:YBfaDhZh4bC6IJfDsEi/8wmtUanir0dMIxpRu3F6Yeo=
|
||||
github.com/gopxl/beep v1.1.0/go.mod h1:N5ClU2N8ESeO6ibbz5UThPRFpdEgbU9G60CLZ6u3v9s=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d h1:Q2+KsA/1GLC9xyLsDun3/EOJ+83rY/IHRsO1DToPrdo=
|
||||
github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d/go.mod h1:RInr+B3/Tx70hYm0rpNPMTD7vH0pBG5ny/JsHAs2KcQ=
|
||||
github.com/grafov/m3u8 v0.12.0 h1:T6iTwTsSEtMcwkayef+FJO8kj+Sglr4Lh81Zj8Ked/4=
|
||||
github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
|
||||
github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 h1:XWjCrg/HJRLZCbvsUxS5R/9JhwiiwNctEsRvZ1Vjz5k=
|
||||
github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690/go.mod h1:8Qu9UmnhCRunfRv365Z3w+mT/WfLGKJiK+vugY9qNCU=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
|
@ -263,94 +219,65 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0=
|
||||
github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgx/v5 v5.1.0 h1:Z7pLKUb65HK6m18No8GGKT87K34NhIIEHa86rRdjxbU=
|
||||
github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||
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/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/youtube/v2 v2.9.0 h1:J7BvfIsxEyyd1MmB/75LgDvG8BGGsG9bSHpbo/qIb+8=
|
||||
github.com/kkdai/youtube/v2 v2.9.0/go.mod h1:H0ntZBgaah4F0wxnEUkLa6yUeyTDDg06xFJ3tvA6gOw=
|
||||
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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/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/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
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/marekm4/color-extractor v1.2.0/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
|
||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||
github.com/markbates/goth v1.76.1 h1:Q2adw0e101v+DlBfxwP7OOjLGkU/pwpNMwu/RYym54w=
|
||||
github.com/markbates/goth v1.76.1/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
||||
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.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/flac v1.0.9 h1:/+wxe/2fA8YbD1kjJNhlVAyc6pWaX0XXZC4xCF5OtVY=
|
||||
github.com/mewkiz/flac v1.0.9/go.mod h1:l7dt5uFY724eKVkHQtAJAQSkhpC3helU3RDxN0ESAqo=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 h1:tnAPMExbRERsyEYkmR1YjhTgDM0iqyiBYf8ojRXxdbA=
|
||||
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14/go.mod h1:QYCFBiH5q6XTHEbWhR0uhR3M9qNPoD2CSQzr0g75kE4=
|
||||
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/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=
|
||||
github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
||||
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/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
|
||||
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -360,32 +287,25 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/romantomjak/shoutcast v1.2.0 h1:MrksaqAd2De3rwvhlkzY8eUEaRYmkvhy9OH0sqM/w74=
|
||||
github.com/romantomjak/shoutcast v1.2.0/go.mod h1:U4Gem8Jbwbz/A2ml2DYOGK0tVpnfQ5sQpSY3UWXcZM4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
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/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
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/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||
github.com/steino/gompd/v2 v2.3.1 h1:hzhzsN18omDnDOJRzSu3KNu/dIyuaJinYE3ICK5bkWs=
|
||||
github.com/steino/gompd/v2 v2.3.1/go.mod h1:6fLLeC5m6Yq/TUQ8e1Va0m+1iSOAFz++X+FbGvneCE4=
|
||||
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
@ -397,22 +317,27 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=
|
||||
github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk=
|
||||
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/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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=
|
||||
|
@ -421,8 +346,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -430,11 +356,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -445,13 +369,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -475,9 +396,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -504,19 +422,15 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -526,8 +440,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -539,9 +453,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -577,23 +489,15 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211001092434-39dca1131b70/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -601,12 +505,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -654,7 +554,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
|
@ -662,13 +561,12 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
|||
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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -685,10 +583,11 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
|
||||
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -727,13 +626,14 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
|
||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -747,10 +647,11 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
|||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
||||
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -763,17 +664,16 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY=
|
||||
gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg=
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM=
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g=
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0 h1:B8lK1KhYrE4H3urNYBAL/UquYftW65IHPY8JP3gpZ4M=
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g=
|
||||
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/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package loop
|
||||
|
||||
import "github.com/gopxl/beep"
|
||||
import "github.com/faiface/beep"
|
||||
|
||||
type loop struct {
|
||||
s beep.StreamSeekCloser
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/effects"
|
||||
|
||||
"dndmusicbot/ffmpeg"
|
||||
discordspeaker "dndmusicbot/speaker"
|
||||
)
|
||||
|
||||
var (
|
||||
pl_volume *effects.Volume
|
||||
amb_volume *effects.Volume
|
||||
amb_mixer beep.Mixer
|
||||
amb_curr Ambiance
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Println("queue.go loading..")
|
||||
|
||||
amb_mixer = beep.Mixer{}
|
||||
|
||||
amb_volume = &effects.Volume{
|
||||
Streamer: &amb_mixer,
|
||||
Base: 2,
|
||||
Volume: 2,
|
||||
Silent: false,
|
||||
}
|
||||
discordspeaker.Play(amb_volume)
|
||||
|
||||
mpdstream, err := NewMPD()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
pl_volume = &effects.Volume{
|
||||
Streamer: mpdstream,
|
||||
Base: 2,
|
||||
Volume: -2,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
discordspeaker.Play(pl_volume)
|
||||
|
||||
/*
|
||||
app.queue = new(Queue)
|
||||
app.queue.list = list.New()
|
||||
app.queue.Events = app.events
|
||||
discordspeaker.Play(app.queue)
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
127
mpd.go
127
mpd.go
|
@ -3,30 +3,33 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
discordspeaker "dndmusicbot/speaker"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/faiface/beep"
|
||||
"github.com/fhs/gompd/v2/mpd"
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/steino/gompd/v2/mpd"
|
||||
)
|
||||
|
||||
type MPD struct {
|
||||
file int
|
||||
f beep.Format
|
||||
httpClient *http.Client
|
||||
pcm *io.PipeReader
|
||||
file int
|
||||
f beep.Format
|
||||
}
|
||||
|
||||
var (
|
||||
mpdcf context.CancelFunc
|
||||
mpdw *mpd.Watcher
|
||||
mpdc *mpd.Client
|
||||
mpd_mutex *sync.Mutex
|
||||
mpd_plcf context.CancelFunc
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Println("mpd.go loading..")
|
||||
|
||||
|
@ -40,9 +43,7 @@ func init() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
mpd_conf := config.GetStringMapString("mpd")
|
||||
|
||||
err = t.Execute(f, mpd_conf)
|
||||
err = t.Execute(f, config.GetStringMapString("mpd"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -50,7 +51,10 @@ func init() {
|
|||
f.Close()
|
||||
|
||||
pidstr, err := os.ReadFile(config.GetString("mpd.pid"))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
switch err {
|
||||
case os.ErrNotExist:
|
||||
log.Println("Pidfile not found, doing nothing")
|
||||
case nil:
|
||||
pid, _ := strconv.Atoi(string(bytes.TrimSpace(pidstr)))
|
||||
proc, err := os.FindProcess(pid)
|
||||
switch err {
|
||||
|
@ -61,13 +65,13 @@ func init() {
|
|||
default:
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
default:
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
app.mpdc = cancel
|
||||
mpdcf = cancel
|
||||
|
||||
cmd := exec.CommandContext(
|
||||
ctx,
|
||||
|
@ -91,65 +95,20 @@ func init() {
|
|||
// wait for mpd to start.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
app.mpd, err = mpd.Dial("unix", config.GetString("mpd.sock"))
|
||||
mpdc, err = mpd.Dial("unix", config.GetString("mpd.sock"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = app.mpd.NewPartition("ambiance")
|
||||
mpdc.Repeat(true)
|
||||
mpdc.Random(true)
|
||||
|
||||
err = mpdc.EnableOutput(0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
app.mpd.Repeat(true)
|
||||
app.mpd.Random(true)
|
||||
|
||||
err = app.mpd.EnableOutput(0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// To force the httpd stream to start.
|
||||
err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
app.mpd.Play(0)
|
||||
time.Sleep(1 * time.Second)
|
||||
app.mpd.Stop()
|
||||
app.mpd.Clear()
|
||||
|
||||
err = app.mpd.Partition("ambiance")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
app.mpd.Repeat(true)
|
||||
|
||||
err = app.mpd.MoveOutput("ambiance")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// To force the httpd stream to start.
|
||||
err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
app.mpd.Play(0)
|
||||
time.Sleep(1 * time.Second)
|
||||
app.mpd.Stop()
|
||||
app.mpd.Clear()
|
||||
err = app.mpd.Partition("default")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
app.mpdw, err = mpd.NewWatcher("unix", config.GetString("mpd.sock"), "")
|
||||
mpdw, err = mpd.NewWatcher("unix", config.GetString("mpd.sock"), "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -157,9 +116,9 @@ func init() {
|
|||
go func() {
|
||||
for {
|
||||
select {
|
||||
case ev := <-app.mpdw.Event:
|
||||
app.events.Emit(events.EventName(ev))
|
||||
case err := <-app.mpdw.Error:
|
||||
case e := <-mpdw.Event:
|
||||
ev.Emit(events.EventName(e))
|
||||
case err := <-mpdw.Error:
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
@ -169,31 +128,14 @@ func init() {
|
|||
log.Println("mpd.go done.")
|
||||
}
|
||||
|
||||
func NewMPD(name string) (*MPD, error) {
|
||||
func NewMPD() (*MPD, error) {
|
||||
out := new(MPD)
|
||||
|
||||
out.httpClient = &http.Client{}
|
||||
|
||||
var pcm *io.PipeWriter
|
||||
out.pcm, pcm = io.Pipe()
|
||||
|
||||
f, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644)
|
||||
f, err := syscall.Open(config.GetString("mpd.fifo"), syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 2048)
|
||||
for {
|
||||
_, err := syscall.Read(f, buf)
|
||||
if err != nil {
|
||||
pcm.Write(make([]byte, 2048))
|
||||
continue
|
||||
}
|
||||
pcm.Write(buf)
|
||||
}
|
||||
}()
|
||||
|
||||
out.f = beep.Format{
|
||||
SampleRate: beep.SampleRate(sampleRate),
|
||||
NumChannels: channels,
|
||||
|
@ -210,17 +152,16 @@ func (m *MPD) Err() error {
|
|||
}
|
||||
|
||||
func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
tmp := make([]byte, m.f.Width())
|
||||
tmp := make([]byte, m.f.NumChannels+2)
|
||||
|
||||
for i := range samples {
|
||||
//dn, err := syscall.Read(m.file, tmp)
|
||||
dn, err := m.pcm.Read(tmp)
|
||||
dn, err := syscall.Read(m.file, tmp)
|
||||
if dn == len(tmp) {
|
||||
samples[i], _ = m.f.DecodeSigned(tmp)
|
||||
ok = true
|
||||
}
|
||||
if err != nil {
|
||||
samples[i] = discordspeaker.Silence
|
||||
samples[i] = [2]float64{}
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/faiface/beep"
|
||||
"github.com/pkg/errors"
|
||||
opusd "gopkg.in/hraban/opus.v2"
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/faiface/beep"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -19,7 +19,6 @@ var (
|
|||
buf []byte
|
||||
maxBytes int = (frameSize * 2) * 2
|
||||
done chan struct{}
|
||||
f beep.Format
|
||||
)
|
||||
|
||||
func update() {
|
||||
|
@ -63,12 +62,6 @@ func TestMain(t *testing.T) {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
f = beep.Format{
|
||||
SampleRate: beep.SampleRate(48000),
|
||||
NumChannels: 2,
|
||||
Precision: 2,
|
||||
}
|
||||
|
||||
loop := beep.Loop(-1, d)
|
||||
|
||||
mu.Lock()
|
||||
|
|
BIN
opus/test.opus
BIN
opus/test.opus
Binary file not shown.
55
queue.go
55
queue.go
|
@ -1,55 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"dndmusicbot/snapcast"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/gopxl/beep/effects"
|
||||
|
||||
discordspeaker "dndmusicbot/speaker"
|
||||
)
|
||||
|
||||
var pl_volume *effects.Volume
|
||||
var amb_volume *effects.Volume
|
||||
|
||||
func init() {
|
||||
log.Println("beep.go loading..")
|
||||
amb := beep.Mixer{}
|
||||
|
||||
amb_volume = &effects.Volume{
|
||||
Streamer: &amb,
|
||||
Base: 2,
|
||||
Volume: -2,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
discordspeaker.Play(amb_volume)
|
||||
|
||||
amb_stream, err := snapcast.New("127.0.0.1", config.GetInt("mpd.ambiance"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
amb.Add(amb_stream)
|
||||
|
||||
pl := beep.Mixer{}
|
||||
|
||||
pl_volume = &effects.Volume{
|
||||
Streamer: &pl,
|
||||
Base: 2,
|
||||
Volume: -2,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
discordspeaker.Play(pl_volume)
|
||||
|
||||
pl_stream, err := snapcast.New("127.0.0.1", config.GetInt("mpd.playlist"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
pl.Add(pl_stream)
|
||||
|
||||
log.Println("beep.go done.")
|
||||
}
|
228
routes.go
228
routes.go
|
@ -1,67 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/markbates/goth/providers/discord"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const COOKIE_NAME = "_dndmusicbot"
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
var router *httprouter.Router
|
||||
|
||||
func init() {
|
||||
key := []byte(os.Getenv("SESSION_SECRET"))
|
||||
maxAge := 86400 * 30 // 30 days
|
||||
isProd := true // Set to true when serving over https
|
||||
router = httprouter.New()
|
||||
|
||||
store := sessions.NewCookieStore([]byte(key))
|
||||
store.MaxAge(maxAge)
|
||||
store.Options.Path = "/"
|
||||
store.Options.HttpOnly = true // HttpOnly should always be enabled
|
||||
store.Options.Secure = isProd
|
||||
router.GET("/", Index)
|
||||
router.GET("/play/:playlist", Play)
|
||||
router.GET("/reset", Reset)
|
||||
router.GET("/public/*js", ServeFiles)
|
||||
router.GET("/css/*css", ServeFiles)
|
||||
|
||||
gothic.Store = store
|
||||
goth.UseProviders(
|
||||
discord.New(config.GetString("discord.id"), config.GetString("discord.secret"), config.GetString("discord.callback"), discord.ScopeIdentify, discord.ScopeEmail, discord.ScopeGuilds, discord.ScopeReadGuilds),
|
||||
)
|
||||
|
||||
app.router = httprouter.New()
|
||||
|
||||
app.router.GET("/", auth(app.Index))
|
||||
app.router.GET("/playlists", auth(app.Web_Playlists))
|
||||
app.router.GET("/ambiance", auth(app.Web_Ambiance))
|
||||
app.router.GET("/play/:playlist", auth(app.Play))
|
||||
app.router.GET("/reset", auth(app.Reset))
|
||||
app.router.GET("/public/*js", auth(app.ServeFiles))
|
||||
app.router.GET("/css/*css", auth(app.ServeFiles))
|
||||
app.router.GET("/auth/callback", app.AuthHandler)
|
||||
app.router.GET("/youtube/:id", local(ProxyTube))
|
||||
|
||||
app.router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("WS connection from %v\n", r.RemoteAddr)
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
|
@ -69,14 +37,14 @@ func init() {
|
|||
return
|
||||
}
|
||||
|
||||
err = ws.join(conn)
|
||||
err = handleWS(conn)
|
||||
if err != nil {
|
||||
log.Printf("WS connection closed, %v\n", r.RemoteAddr)
|
||||
}
|
||||
}))
|
||||
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(":"+config.GetString("web.port"), app.router))
|
||||
log.Fatal(http.ListenAndServe(":8824", router))
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -85,139 +53,7 @@ type IndexData struct {
|
|||
Ambiance []Ambiance
|
||||
}
|
||||
|
||||
func (app App) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
user, err := gothic.CompleteUserAuth(w, r)
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
profile_url := &url.URL{Scheme: "https",
|
||||
Host: "discord.com",
|
||||
Path: "/api/users/@me",
|
||||
}
|
||||
member_url := fmt.Sprintf("%s/guilds/%s/member", profile_url.String(), config.GetString("discord.guild"))
|
||||
member_req, err := http.NewRequest("GET", member_url, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
member_req.Header.Add("Authorization", "Bearer "+user.AccessToken)
|
||||
member_resp, err := http.DefaultClient.Do(member_req)
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
member_body, _ := io.ReadAll(member_resp.Body)
|
||||
defer member_resp.Body.Close()
|
||||
|
||||
member, err := simplejson.NewJson(member_body)
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
groups, err := member.GetPath("roles").StringArray()
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ok := false
|
||||
|
||||
for _, group := range config.GetStringSlice("discord.groups") {
|
||||
if slices.Contains(groups, group) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"nbf": time.Now().Unix(),
|
||||
"exp": time.Now().Add(time.Hour * 720).Unix(),
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("SESSION_SECRET")))
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = COOKIE_NAME
|
||||
cookie.Value = tokenString
|
||||
cookie.Path = "/"
|
||||
cookie.Secure = true
|
||||
cookie.HttpOnly = true
|
||||
cookie.MaxAge = 86400 * 30
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
http.RedirectHandler("/", http.StatusFound).ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware to check that the connection is local.
|
||||
func local(n httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
addr, err := netip.ParseAddrPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case addr.Addr().IsLoopback():
|
||||
fallthrough
|
||||
case addr.Addr().IsPrivate():
|
||||
n(w, r, ps)
|
||||
default:
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func auth(n httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
if os.Getenv("APP_ENV") == "test" {
|
||||
n(w, r, ps)
|
||||
return
|
||||
}
|
||||
values := r.URL.Query()
|
||||
values.Add("provider", "discord")
|
||||
r.URL.RawQuery = values.Encode()
|
||||
|
||||
auth_cookie, err := r.Cookie(COOKIE_NAME)
|
||||
if err == nil {
|
||||
token, err := jwt.Parse(auth_cookie.Value, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(os.Getenv("SESSION_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if token.Valid {
|
||||
n(w, r, ps)
|
||||
} else {
|
||||
gothic.BeginAuthHandler(w, r)
|
||||
return
|
||||
}
|
||||
} else if err == http.ErrNoCookie {
|
||||
gothic.BeginAuthHandler(w, r)
|
||||
return
|
||||
} else if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app App) ServeFiles(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
func ServeFiles(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
filePath := filepath.Join(".", r.URL.Path)
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
|
@ -239,8 +75,8 @@ func (app App) ServeFiles(w http.ResponseWriter, r *http.Request, _ httprouter.P
|
|||
http.ServeContent(w, r, filename, t, file)
|
||||
}
|
||||
|
||||
func (app App) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
playlists, err := app.GetPlaylists()
|
||||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
playlists, err := GetPlaylists()
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -260,35 +96,11 @@ func (app App) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params
|
|||
}
|
||||
}
|
||||
|
||||
func (app App) Web_Playlists(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
playlists, err := app.GetPlaylists()
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(playlists)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (app App) Web_Ambiance(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
ambiance, err := GetAmbiances()
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to get ambiance. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(ambiance)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) Play(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
func Play(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
plname := p.ByName("playlist")
|
||||
|
||||
if plname == "reset" {
|
||||
app.events.Emit("stop", nil)
|
||||
ev.Emit("stop", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -297,14 +109,14 @@ func (app *App) Play(w http.ResponseWriter, r *http.Request, p httprouter.Params
|
|||
http.Error(w, "Unable to parse uuid. "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
app.events.Emit("new_playlist", plid)
|
||||
ev.Emit("new_playlist", plid)
|
||||
}
|
||||
|
||||
func (app *App) Add(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
func Add(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
r.ParseForm()
|
||||
|
||||
}
|
||||
|
||||
func (app *App) Reset(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
app.events.Emit("stop", nil)
|
||||
func Reset(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
ev.Emit("stop", nil)
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
package snapcast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/gopxl/beep"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
sync.Mutex
|
||||
|
||||
Out io.ReadCloser
|
||||
Cancel context.CancelFunc
|
||||
Cmd *exec.Cmd
|
||||
|
||||
f beep.Format
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func New(host string, port int) (ff beep.Streamer, err error) {
|
||||
st := new(Stream)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
st.Cancel = cancel
|
||||
|
||||
st.Cmd = exec.CommandContext(
|
||||
ctx,
|
||||
"/usr/bin/snapclient",
|
||||
"-h", host,
|
||||
"-p", strconv.Itoa(port),
|
||||
"--player", "file:filename=stderr",
|
||||
"--logfilter", "*:error",
|
||||
)
|
||||
|
||||
st.Cmd.Stdout = os.Stdin
|
||||
|
||||
st.Out, err = st.Cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = st.Cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st.buf = make([]byte, 4)
|
||||
st.f = beep.Format{
|
||||
SampleRate: beep.SampleRate(48000),
|
||||
NumChannels: 2,
|
||||
Precision: 2,
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (d *Stream) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
for i := range samples {
|
||||
_, err := d.Out.Read(d.buf)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
samples[i], _ = d.f.DecodeSigned(d.buf)
|
||||
ok = true
|
||||
}
|
||||
|
||||
return len(samples), ok
|
||||
}
|
||||
|
||||
func (d *Stream) Err() error {
|
||||
return nil
|
||||
}
|
|
@ -2,60 +2,48 @@ package discordspeaker
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/arikawa/v3/voice"
|
||||
"github.com/diamondburned/arikawa/v3/voice/voicegateway"
|
||||
"github.com/gopxl/beep"
|
||||
"github.com/faiface/beep"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/time/rate"
|
||||
"gopkg.in/hraban/opus.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
mixer beep.Mixer
|
||||
samples [][2]float64
|
||||
done chan struct{}
|
||||
encoder *opus.Encoder
|
||||
mu sync.Mutex
|
||||
mixer beep.Mixer
|
||||
samples [][2]float64
|
||||
done chan struct{}
|
||||
encoder *opus.Encoder
|
||||
//voice *discordgo.VoiceConnection
|
||||
frameSize int = 960
|
||||
channels int = 2
|
||||
sampleRate int = 48000
|
||||
maxBytes int = (frameSize * 2) * 2
|
||||
buf []byte
|
||||
session *voice.Session
|
||||
pw *io.PipeWriter
|
||||
pr *io.PipeReader
|
||||
|
||||
spklimit = rate.NewLimiter(rate.Every(5*time.Second), 1)
|
||||
spk = true
|
||||
spk bool
|
||||
)
|
||||
|
||||
var start time.Time
|
||||
|
||||
var Silence = [2]float64{}
|
||||
|
||||
var dmutex = sync.Mutex{}
|
||||
|
||||
func Init(dgv *voice.Session, bitrate int) error {
|
||||
func Init(dgv *voice.Session) error {
|
||||
var err error
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
Close()
|
||||
|
||||
pr, pw = io.Pipe()
|
||||
buf = make([]byte, maxBytes)
|
||||
mixer = beep.Mixer{}
|
||||
buf = make([]byte, maxBytes)
|
||||
samples = make([][2]float64, frameSize)
|
||||
|
||||
session = dgv
|
||||
|
||||
encoder, err = opus.NewEncoder(sampleRate, channels, opus.AppAudio)
|
||||
encoder.SetBitrate(bitrate)
|
||||
encoder, err = opus.NewEncoder(sampleRate, channels, opus.AppVoIP)
|
||||
encoder.SetBitrateToMax()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize speaker")
|
||||
|
@ -72,16 +60,6 @@ func Init(dgv *voice.Session, bitrate int) error {
|
|||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
dmutex.Lock()
|
||||
_, err := io.Copy(session, pr)
|
||||
dmutex.Unlock()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -109,74 +87,56 @@ func Clear() {
|
|||
mu.Unlock()
|
||||
}
|
||||
|
||||
func Speak(s bool) {
|
||||
switch s {
|
||||
case true:
|
||||
func update() {
|
||||
mu.Lock()
|
||||
mixer.Stream(samples)
|
||||
mu.Unlock()
|
||||
var f32 []float32
|
||||
|
||||
case false:
|
||||
for _, sample := range samples {
|
||||
for _, val := range sample {
|
||||
f32 = append(f32, float32(val))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func CheckSilence(samples [][2]float64) {
|
||||
if IsSilent(samples) {
|
||||
if spk && spklimit.Allow() {
|
||||
if Silence(f32) {
|
||||
if spk {
|
||||
log.Println("Notspeaking")
|
||||
session.Speaking(context.Background(), voicegateway.NotSpeaking)
|
||||
spk = false
|
||||
}
|
||||
} else {
|
||||
if !spk {
|
||||
log.Println("Speaking")
|
||||
session.Speaking(context.Background(), voicegateway.Microphone)
|
||||
spk = true
|
||||
spklimit.Reserve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update() {
|
||||
start = time.Now()
|
||||
mu.Lock()
|
||||
mixer.Stream(samples)
|
||||
mu.Unlock()
|
||||
|
||||
go CheckSilence(samples)
|
||||
|
||||
if !spk {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
f32 := make([]float32, len(samples)*2)
|
||||
var idx int
|
||||
|
||||
for i := 0; i < len(samples); i++ {
|
||||
f32[idx] = float32(samples[i][0])
|
||||
idx++
|
||||
f32[idx] = float32(samples[i][1])
|
||||
idx++
|
||||
if !spk {
|
||||
log.Println("Speaking")
|
||||
session.Speaking(context.Background(), voicegateway.Microphone)
|
||||
spk = true
|
||||
}
|
||||
|
||||
n, err := encoder.EncodeFloat32(f32, buf)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = pw.Write(buf[:n])
|
||||
_, err = session.Write(buf[:n])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
//fmt.Println(time.Since(start), len(samples), n)
|
||||
|
||||
}
|
||||
|
||||
func IsSilent(in [][2]float64) bool {
|
||||
func Silence(in []float32) bool {
|
||||
for _, v := range in {
|
||||
if v != Silence {
|
||||
if v != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import CSS from "csstype";
|
||||
import { on } from "./events";
|
||||
import ws from "./ws";
|
||||
import SortableList, { SortableItem } from "react-easy-sort";
|
||||
import { arrayMoveImmutable } from "array-move";
|
||||
|
||||
export default function Ambiance() {
|
||||
interface Ambiance {
|
||||
Id: string;
|
||||
Title: string;
|
||||
}
|
||||
|
||||
const [ambiance, setAmbiance] = useState<Ambiance[]>([]);
|
||||
const [active, setActive] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [percent, setPercent] = useState(0);
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
const onSortEnd = (oldIndex: number, newIndex: number) => {
|
||||
setAmbiance((array) => arrayMoveImmutable(array, oldIndex, newIndex));
|
||||
};
|
||||
|
||||
const activeStyle: CSS.Properties = {
|
||||
backgroundColor: "burlywood",
|
||||
};
|
||||
|
||||
const getOrder = (name: string) => {
|
||||
var order = localStorage.getItem("dndmusicbot-" + name);
|
||||
return order ? order.split("|") : [];
|
||||
};
|
||||
|
||||
const setOrder = (name: string) => {
|
||||
var order = ambiance.map((a) => a.Id);
|
||||
localStorage.setItem("dndmusicbot-" + name, order.join("|"));
|
||||
};
|
||||
|
||||
const fetchAmbiance = () => {
|
||||
const order = getOrder("ambiance");
|
||||
|
||||
fetch("/ambiance")
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
data.sort(
|
||||
(a: Ambiance, b: Ambiance) =>
|
||||
order.indexOf(a.Id) - order.indexOf(b.Id)
|
||||
);
|
||||
setAmbiance(data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAmbiance();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setOrder("ambiance");
|
||||
}, [ambiance]);
|
||||
|
||||
const Play = (e: any) => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "ambiance_play",
|
||||
payload: e.target.dataset.id,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const Stop = () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "ambiance_stop",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const AddAmbiance = () => {
|
||||
if (title == "" || url == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "ambiance_add",
|
||||
payload: {
|
||||
title: title,
|
||||
url: url,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
on("dnd:ambiance_add", () => fetchAmbiance());
|
||||
on("dnd:ambiance_add_start", () => setRunning(true));
|
||||
on("dnd:ambiance_add_finish", () => {
|
||||
setRunning(false);
|
||||
setPercent(0);
|
||||
setTitle("");
|
||||
setUrl("");
|
||||
});
|
||||
on("dnd:ambiance_play", (e: any) => setActive(e.detail.id));
|
||||
on("dnd:ambiance_stop", () => setActive(""));
|
||||
on("dnd:ambiance_encode_finish", () => setPercent(0));
|
||||
on("dnd:ambiance_encode_progress", (e: any) => {
|
||||
setRunning(true);
|
||||
|
||||
const p = parseInt(e.detail.percent, 10);
|
||||
if (!Number.isNaN(p)) {
|
||||
setPercent(p);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="bot">Ambiance</h2>
|
||||
<section>
|
||||
<SortableList
|
||||
onSortEnd={onSortEnd}
|
||||
className="item-container"
|
||||
draggedItemClassName="dragged"
|
||||
>
|
||||
{ambiance.map((item) => {
|
||||
return (
|
||||
<SortableItem key={item.Id}>
|
||||
<div
|
||||
className="item drag"
|
||||
data-id={item.Id}
|
||||
style={item.Id == active ? activeStyle : {}}
|
||||
onClick={Play}
|
||||
>
|
||||
{item.Title}
|
||||
</div>
|
||||
</SortableItem>
|
||||
);
|
||||
})}
|
||||
<div className="item locked stop" data-id="reset" onClick={Stop}>
|
||||
Stop
|
||||
</div>
|
||||
</SortableList>
|
||||
</section>
|
||||
<section>
|
||||
<div id="progressambiance" className="input-container">
|
||||
<progress
|
||||
max="100"
|
||||
value={percent}
|
||||
style={{ width: "100%" }}
|
||||
className={running ? "" : "u-hidden"}
|
||||
>
|
||||
{percent}%
|
||||
</progress>
|
||||
</div>
|
||||
</section>
|
||||
<section className={running ? "u-hidden" : ""}>
|
||||
<div id="inputambiance" className="input-container">
|
||||
<input
|
||||
className="u-full-width"
|
||||
name="title"
|
||||
type="text"
|
||||
value={title}
|
||||
placeholder="Enter name.."
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
disabled={running}
|
||||
/>
|
||||
<input
|
||||
className="u-full-width"
|
||||
name="url"
|
||||
type="text"
|
||||
value={url}
|
||||
placeholder="Enter url..."
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
disabled={running}
|
||||
/>
|
||||
<input
|
||||
id="addambiance"
|
||||
name="submit"
|
||||
value="Add"
|
||||
type="submit"
|
||||
onClick={AddAmbiance}
|
||||
disabled={running}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import CSS from "csstype";
|
||||
import { on } from "./events";
|
||||
import ws from "./ws";
|
||||
|
||||
export default function Controls() {
|
||||
const [channel, setChannel] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const [pos, setPos] = useState(0);
|
||||
const [len, setLen] = useState(0);
|
||||
const [song, setSong] = useState("");
|
||||
const [pause, setPause] = useState(true);
|
||||
|
||||
const linkStyle: CSS.Properties = {
|
||||
textDecoration: "none",
|
||||
};
|
||||
|
||||
const Next = () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "next",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const Prev = () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "next",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const msToTime = (duration: number) => {
|
||||
var milliseconds = (duration % 1000) / 100,
|
||||
seconds = Math.floor((duration / 1000) % 60),
|
||||
minutes = Math.floor((duration / (1000 * 60)) % 60),
|
||||
minutes = minutes < 10 ? 0 + minutes : minutes;
|
||||
seconds = seconds < 10 ? 0 + seconds : seconds;
|
||||
|
||||
return (
|
||||
minutes.toString().padStart(2, "0") +
|
||||
":" +
|
||||
seconds.toString().padStart(2, "0")
|
||||
);
|
||||
};
|
||||
|
||||
on("dnd:song_info", (e: any) => {
|
||||
setChannel(e.detail.channel);
|
||||
setTitle(e.detail.current);
|
||||
setPos(e.detail.position);
|
||||
setLen(e.detail.len);
|
||||
setSong("https://youtu.be/" + e.detail.song);
|
||||
setPause(e.detail.pause);
|
||||
});
|
||||
|
||||
on("dnd:song_position", (e: any) => {
|
||||
setPos(e.detail.position);
|
||||
setLen(e.detail.len);
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div id="info">
|
||||
<a
|
||||
href={song}
|
||||
id="link"
|
||||
style={linkStyle}
|
||||
className={pause ? "u-hidden" : ""}
|
||||
>
|
||||
<span id="channel">{channel}</span>
|
||||
<span> - </span>
|
||||
<span id="title">{title}</span>
|
||||
</a>
|
||||
|
||||
<div className={pause ? "controls u-hidden" : "controls"}>
|
||||
<input
|
||||
id="prev"
|
||||
name="prev"
|
||||
type="button"
|
||||
value="prev"
|
||||
onClick={Prev}
|
||||
/>
|
||||
<span id="time">
|
||||
{msToTime(pos)} / {msToTime(len)}
|
||||
</span>
|
||||
<input
|
||||
id="next"
|
||||
name="next"
|
||||
type="button"
|
||||
value="next"
|
||||
onClick={Next}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
function on(eventType: any, listener: { (event: any): void; (this: Document, ev: any): any; }) {
|
||||
document.addEventListener(eventType, listener);
|
||||
}
|
||||
|
||||
function off(eventType: any, listener: { (event: any): void; (this: Document, ev: any): any; }) {
|
||||
document.removeEventListener(eventType, listener);
|
||||
}
|
||||
|
||||
function once(eventType: any, listener: (arg0: any) => void) {
|
||||
on(eventType, handleEventOnce);
|
||||
|
||||
function handleEventOnce(event: any) {
|
||||
listener(event);
|
||||
off(eventType, handleEventOnce);
|
||||
}
|
||||
}
|
||||
|
||||
function trigger(eventType: string, data: any) {
|
||||
const event = new CustomEvent(eventType, { detail: data });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
export { on, once, off, trigger };
|
|
@ -1,17 +0,0 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
export const useDebounce = (value: any, milliSeconds: number) => {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, milliSeconds);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, milliSeconds]);
|
||||
|
||||
return debouncedValue;
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import ws from "./ws";
|
||||
|
||||
export default function Modal() {
|
||||
const [waiting, setWaiting] = useState(true);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("false");
|
||||
setWaiting(false);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("true");
|
||||
setWaiting(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="waiting" className={waiting ? "modal" : "modal u-hidden"}>
|
||||
<div className="modal-content">
|
||||
<p>Waiting for bot..</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import CSS from "csstype";
|
||||
import { on } from "./events";
|
||||
import ws from "./ws";
|
||||
import SortableList, { SortableItem } from "react-easy-sort";
|
||||
import { arrayMoveImmutable } from "array-move";
|
||||
|
||||
export default function Playlists() {
|
||||
interface Playlist {
|
||||
Id: string;
|
||||
Title: string;
|
||||
}
|
||||
|
||||
const [playlists, setPlaylists] = useState<Playlist[]>([]);
|
||||
const [active, setActive] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [output, setOutput] = useState("");
|
||||
|
||||
const onSortEnd = (oldIndex: number, newIndex: number) => {
|
||||
setPlaylists((array) => arrayMoveImmutable(array, oldIndex, newIndex));
|
||||
};
|
||||
|
||||
const activeStyle: CSS.Properties = {
|
||||
backgroundColor: "burlywood",
|
||||
};
|
||||
|
||||
const getOrder = (name: string) => {
|
||||
var order = localStorage.getItem("dndmusicbot-" + name);
|
||||
return order ? order.split("|") : [];
|
||||
};
|
||||
|
||||
const setOrder = (name: string) => {
|
||||
var order = playlists.map((a) => a.Id);
|
||||
localStorage.setItem("dndmusicbot-" + name, order.join("|"));
|
||||
};
|
||||
|
||||
const fetchPlaylists = () => {
|
||||
const order = getOrder("playlist");
|
||||
|
||||
fetch("/playlists")
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
data.sort(
|
||||
(a: Playlist, b: Playlist) =>
|
||||
order.indexOf(a.Id) - order.indexOf(b.Id)
|
||||
);
|
||||
setPlaylists(data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlaylists();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setOrder("playlist");
|
||||
}, [playlists]);
|
||||
|
||||
const Play = (e: any) => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "load_playlist",
|
||||
payload: e.target.dataset.id,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const Stop = () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "stop",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const AddPlaylist = () => {
|
||||
if (title == "" || url == "") {
|
||||
setOutput("Title or Url is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "add_playlist",
|
||||
payload: {
|
||||
title: title,
|
||||
url: url,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
on("dnd:song_info", (e: any) => {
|
||||
setActive(e.detail.playlist);
|
||||
});
|
||||
|
||||
on("dnd:new_playlist", (e: any) => {
|
||||
fetchPlaylists();
|
||||
setOutput("New playlist was added: " + e.details.title);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="bot">Playlists</h2>
|
||||
<section>
|
||||
<SortableList
|
||||
onSortEnd={onSortEnd}
|
||||
className="item-container"
|
||||
draggedItemClassName="dragged"
|
||||
>
|
||||
{playlists.map((playlist) => {
|
||||
return (
|
||||
<SortableItem key={playlist.Id}>
|
||||
<div
|
||||
onClick={Play}
|
||||
className="item"
|
||||
data-id={playlist.Id}
|
||||
style={playlist.Id == active ? activeStyle : {}}
|
||||
>
|
||||
{playlist.Title}
|
||||
</div>
|
||||
</SortableItem>
|
||||
);
|
||||
})}
|
||||
<div className="item locked stop" onClick={Stop} data-id="reset">
|
||||
Stop
|
||||
</div>
|
||||
</SortableList>
|
||||
</section>
|
||||
<section>
|
||||
<div id="inputplaylist" className="input-container">
|
||||
<input
|
||||
className="u-full-width"
|
||||
name="title"
|
||||
type="text"
|
||||
placeholder="Enter name.."
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="u-full-width"
|
||||
name="url"
|
||||
type="text"
|
||||
placeholder="https://youtube.com/playlist?list=..."
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
id="addplaylist"
|
||||
name="submit"
|
||||
value="Add"
|
||||
type="submit"
|
||||
onClick={AddPlaylist}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<p>{output}</p>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { trigger, on } from "./events";
|
||||
import Playlists from "./playlist";
|
||||
import ws from "./ws";
|
||||
import Controls from "./controls";
|
||||
import Ambiance from "./ambiance";
|
||||
import Volumes from "./volume";
|
||||
import Modal from "./modal";
|
||||
|
||||
function Content() {
|
||||
ws.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
trigger("dnd:" + data.event, data.payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Playlists />
|
||||
<Controls />
|
||||
<Ambiance />
|
||||
<Volumes />
|
||||
<Modal />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const domNode = document.getElementById("content");
|
||||
const root = createRoot(domNode!);
|
||||
root.render(<Content />);
|
|
@ -1,111 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { on } from "./events";
|
||||
import ws from "./ws";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useDebounce } from "./hooks/useDebounce";
|
||||
|
||||
export default function Volumes() {
|
||||
const [playlist, setPlaylist] = useState(0);
|
||||
const [ambiance, setAmbiance] = useState(0);
|
||||
|
||||
const plfirstUpdate = useRef(true);
|
||||
const ambfirstUpdate = useRef(true);
|
||||
const plsend = useDebounce(playlist, 100);
|
||||
const ambsend = useDebounce(ambiance, 100);
|
||||
|
||||
on("dnd:volume", (e: any) => {
|
||||
setPlaylist(e.detail.playlist);
|
||||
setAmbiance(e.detail.ambiance);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (plfirstUpdate.current) {
|
||||
plfirstUpdate.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "vol_set",
|
||||
payload: {
|
||||
type: "playlist",
|
||||
vol: plsend.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [plsend]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ambfirstUpdate.current) {
|
||||
ambfirstUpdate.current = false;
|
||||
return;
|
||||
}
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: "vol_set",
|
||||
payload: {
|
||||
type: "ambiance",
|
||||
vol: ambsend.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [ambsend]);
|
||||
|
||||
const PlaylistVol = (e: any) => {
|
||||
setPlaylist(parseFloat(e.target.value));
|
||||
};
|
||||
const AmbianceVol = (e: any) => {
|
||||
setAmbiance(parseFloat(e.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<div id="volume_playlist" className="input-container">
|
||||
<label htmlFor="playlist-volume">Playlist</label>
|
||||
<input
|
||||
type="range"
|
||||
id="playlist-volume"
|
||||
min="-10"
|
||||
max="4"
|
||||
step="0.1"
|
||||
value={playlist}
|
||||
onChange={PlaylistVol}
|
||||
/>
|
||||
<input
|
||||
id="playlist-volume-number"
|
||||
type="number"
|
||||
min="-10"
|
||||
max="4"
|
||||
step="0.1"
|
||||
style={{ width: "50px" }}
|
||||
value={playlist}
|
||||
onChange={PlaylistVol}
|
||||
/>
|
||||
</div>
|
||||
<div id="volume_ambiance" className="input-container">
|
||||
<label htmlFor="ambiance-volume">Ambiance</label>
|
||||
<input
|
||||
type="range"
|
||||
id="ambiance-volume"
|
||||
min="-10"
|
||||
max="4"
|
||||
step="0.1"
|
||||
value={ambiance}
|
||||
onChange={AmbianceVol}
|
||||
/>
|
||||
<input
|
||||
id="ambiance-volume-number"
|
||||
type="number"
|
||||
min="-10"
|
||||
max="4"
|
||||
step="0.1"
|
||||
style={{ width: "50px" }}
|
||||
value={ambiance}
|
||||
onChange={AmbianceVol}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||
|
||||
const ws = new ReconnectingWebSocket(
|
||||
(window.location.protocol === "https:" ? "wss://" : "ws://") +
|
||||
window.location.host +
|
||||
"/ws"
|
||||
);
|
||||
|
||||
export default ws;
|
|
@ -1,20 +1,9 @@
|
|||
{
|
||||
"scripts": {
|
||||
"build": "esbuild app/root.tsx --bundle --outfile=/public/script.js"
|
||||
"build": "esbuild script.js --bundle --outdir=/public/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^18.2.33",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"array-move": "^4.0.0",
|
||||
"esbuild": "^0.15.14",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-debounce-input": "^3.3.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-easy-sort": "^1.6.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"sortablejs": "^1.15.0"
|
||||
}
|
||||
|
|
|
@ -147,39 +147,6 @@ window.onload = function () {
|
|||
avol.value = data.payload.ambiance
|
||||
avol.nextElementSibling.value = data.payload.ambiance
|
||||
break
|
||||
case "ambiance_download_start":
|
||||
var progress = document.querySelector("#progressambiance progress")
|
||||
progress.style.display = "initial"
|
||||
progress.style.width = "100%"
|
||||
progress.value = 0
|
||||
break
|
||||
case "ambiance_download_progress":
|
||||
var progress = document.querySelector("#progressambiance progress")
|
||||
progress.style.display = "initial"
|
||||
progress.value = data.payload.percent
|
||||
console.log(data)
|
||||
break
|
||||
case "ambiance_download_complete":
|
||||
var progress = document.querySelector("#progressambiance progress")
|
||||
progress.style.display = "initial"
|
||||
progress.value = 100
|
||||
break
|
||||
case "ambiance_add_finish":
|
||||
var title = document.querySelector("#inputambiance > input[name='title']")
|
||||
var url = document.querySelector("#inputambiance > input[name='url']")
|
||||
var submit = document.querySelector("#inputambiance > input[name='submit']")
|
||||
var progress = document.querySelector("#progressambiance progress")
|
||||
|
||||
title.value = ""
|
||||
url.value = ""
|
||||
|
||||
title.disabled = false
|
||||
url.disabled = false
|
||||
submit.disabled = false
|
||||
|
||||
progress.value = 0
|
||||
progress.style.display = "none"
|
||||
break
|
||||
case "ambiance_add":
|
||||
const container = document.querySelector("#ambiance")
|
||||
var newdiv = document.createElement('div');
|
||||
|
@ -197,21 +164,6 @@ window.onload = function () {
|
|||
})
|
||||
|
||||
container.insertBefore(newdiv, document.querySelector("#ambiance div:last-child"))
|
||||
var title = document.querySelector("#inputambiance > input[name='title']")
|
||||
var url = document.querySelector("#inputambiance > input[name='url']")
|
||||
var submit = document.querySelector("#inputambiance > input[name='submit']")
|
||||
var progress = document.querySelector("#progressambiance progress")
|
||||
|
||||
title.value = ""
|
||||
url.value = ""
|
||||
|
||||
title.disabled = false
|
||||
url.disabled = false
|
||||
submit.disabled = false
|
||||
|
||||
progress.value = 0
|
||||
progress.style.display = "none"
|
||||
|
||||
break
|
||||
case "ambiance_play":
|
||||
document.querySelectorAll("#ambiance > div").forEach((e) => {e.style.removeProperty("background-color")})
|
||||
|
@ -266,7 +218,6 @@ window.onload = function () {
|
|||
//output.innerText = ""
|
||||
var title = document.querySelector("#inputambiance > input[name='title']")
|
||||
var url = document.querySelector("#inputambiance > input[name='url']")
|
||||
var submit = document.querySelector("#inputambiance > input[name='submit']")
|
||||
if (title.value == "" || url.value == "") {
|
||||
console.log("Title or Url is empty!")
|
||||
return
|
||||
|
@ -280,9 +231,8 @@ window.onload = function () {
|
|||
}
|
||||
}))
|
||||
|
||||
title.disabled = true
|
||||
url.disabled = true
|
||||
submit.disabled = true
|
||||
title.value = ""
|
||||
url.value = ""
|
||||
})
|
||||
|
||||
addInteractHandler(submit, (e, isTouch) => {
|
||||
|
|
284
src/yarn.lock
284
src/yarn.lock
|
@ -2,13 +2,6 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.9.2":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
|
||||
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@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"
|
||||
|
@ -19,96 +12,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz#1221684955c44385f8af34f7240088b7dc08d19d"
|
||||
integrity sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==
|
||||
|
||||
"@react-dnd/asap@^5.0.1":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488"
|
||||
integrity sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==
|
||||
|
||||
"@react-dnd/invariant@^4.0.1":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-4.0.2.tgz#b92edffca10a26466643349fac7cdfb8799769df"
|
||||
integrity sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==
|
||||
|
||||
"@react-dnd/shallowequal@^4.0.1":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4"
|
||||
integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.4.tgz#cc477ce0283bb9d19ea0cbfa2941fe2c8493a1be"
|
||||
integrity sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d"
|
||||
integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==
|
||||
|
||||
"@types/react-dom@^18.2.14":
|
||||
version "18.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539"
|
||||
integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.1.20":
|
||||
version "7.1.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.28.tgz#30a44303c7daceb6ede9cfb4aaf72e64f1dde4de"
|
||||
integrity sha512-EQr7cChVzVUuqbA+J8ArWK1H0hLAHKOs21SIMrskKZ3nHNeE+LFYA+IsoZGhVOT8Ktjn3M20v4rnZKN3fLbypw==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react@*", "@types/react@^18.2.33":
|
||||
version "18.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.33.tgz#055356243dc4350a9ee6c6a2c07c5cae12e38877"
|
||||
integrity sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/scheduler@*":
|
||||
version "0.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.5.tgz#4751153abbf8d6199babb345a52e1eb4167d64af"
|
||||
integrity sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==
|
||||
|
||||
array-move@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/array-move/-/array-move-3.0.1.tgz#179645cc0987b65953a4fc06b6df9045e4ba9618"
|
||||
integrity sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==
|
||||
|
||||
array-move@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-move/-/array-move-4.0.0.tgz#2c3730f056cc926f62a59769a5a8cda2fb6d8c55"
|
||||
integrity sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==
|
||||
|
||||
css-box-model@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||
dependencies:
|
||||
tiny-invariant "^1.0.6"
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||
|
||||
dnd-core@^16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-16.0.1.tgz#a1c213ed08961f6bd1959a28bb76f1a868360d19"
|
||||
integrity sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==
|
||||
dependencies:
|
||||
"@react-dnd/asap" "^5.0.1"
|
||||
"@react-dnd/invariant" "^4.0.1"
|
||||
redux "^4.2.0"
|
||||
|
||||
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"
|
||||
|
@ -237,199 +140,12 @@ esbuild@^0.15.14:
|
|||
esbuild-windows-64 "0.15.14"
|
||||
esbuild-windows-arm64 "0.15.14"
|
||||
|
||||
fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
lodash.debounce@^4:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
memoize-one@^5.1.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
prop-types@^15.5.7, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
raf-schd@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||
|
||||
react-beautiful-dnd@^13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
||||
integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
css-box-model "^1.2.0"
|
||||
memoize-one "^5.1.1"
|
||||
raf-schd "^4.0.2"
|
||||
react-redux "^7.2.0"
|
||||
redux "^4.0.4"
|
||||
use-memo-one "^1.1.1"
|
||||
|
||||
react-debounce-input@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-debounce-input/-/react-debounce-input-3.3.0.tgz#85e3ebcaa41f2016e50613134a1ec9fe3cdb422e"
|
||||
integrity sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==
|
||||
dependencies:
|
||||
lodash.debounce "^4"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-dnd-html5-backend@^16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6"
|
||||
integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==
|
||||
dependencies:
|
||||
dnd-core "^16.0.1"
|
||||
|
||||
react-dnd@^16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37"
|
||||
integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==
|
||||
dependencies:
|
||||
"@react-dnd/invariant" "^4.0.1"
|
||||
"@react-dnd/shallowequal" "^4.0.1"
|
||||
dnd-core "^16.0.1"
|
||||
fast-deep-equal "^3.1.3"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-easy-sort@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-easy-sort/-/react-easy-sort-1.6.0.tgz#b40cce827913f0640c1b2e5438dd4d007e26db32"
|
||||
integrity sha512-zd9Nn90wVlZPEwJrpqElN87sf9GZnFR1StfjgNQVbSpR5QTSzCHjEYK6REuwq49Ip+76KOMSln9tg/ST2KLelg==
|
||||
dependencies:
|
||||
array-move "^3.0.1"
|
||||
tslib "2.0.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.9"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
|
||||
integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/react-redux" "^7.1.20"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-sortable-hoc@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz#f6780d8aa4b922a21f3e754af542f032677078b7"
|
||||
integrity sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.2.0"
|
||||
invariant "^2.2.4"
|
||||
prop-types "^15.5.7"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
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==
|
||||
|
||||
redux@^4.0.0, redux@^4.0.4, redux@^4.2.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
regenerator-runtime@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
|
||||
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
|
||||
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
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==
|
||||
|
||||
tiny-invariant@^1.0.6:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
|
||||
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
|
||||
|
||||
tslib@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
|
||||
integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
|
||||
|
||||
use-memo-one@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
|
||||
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
|
||||
|
|
|
@ -10,7 +10,84 @@
|
|||
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAACNklEQVQ4jaWSy0vUcRTFP/f7G4cUUqzAZtQsgzYWiDMjjYgotbOtYLSIFlKBix7YH9DWXQaRFRREi8hN9MCFPZDUzB4IUkRo5Oho1CLTcEbne1o0UWCI0FndC+cezr33wH/C/m5mkvFOw04JIsBrmXVVDI0OryfgfhepZH0bWA/wFaMLqDXp/nQyuWVDAhi1wIz36k5n/BXgMVAamI+vJxD6o7TS61Uw7QLbEXH+OGIOQzmtTG7IgVfBPjOdE6oXdtrQd8mOVQ6/+vCbM9XcvGmipia8xsFsLLZN6DBQZWIRKJPZISd5gLFYrCBaYF3KLB0IFxdVzyQTZYZukNXZEEA2CDaH8SUS5zHbBVZo+AE5OzrXEL+eyxHxZo2IyzL/FqywcnhsFCCktrYgnZrqWc35nsCCqHM2mJMWkXuD+RVPUIvzRxB3K0Ze3F5zg8/pj1XCEkEQ9OHoZnH5uaFAYfXjrcmhUkPvyit39v7zC9lcKO1Y3QoEwOD28fEl4CRAqiFxJyddAHuUnpwsnW9MFIdW3JeMW41Ghl++N/AOMuX5YTCqlU/nbCxWZLDXQRfQRNiNr+Z0L+OUFHZwNhm/CRBaWMh8Ki0u+iYoQeyZ3Z+4lcKeytSBqPOyZ2a+X1grWESoE6wFWAZwNRMTWaEzBk/yLtrNdAmow7gaHRm96HEpYCC/divGjGEdv+h5qLk5lM4utUvWAvxw8g8iI2MPAeYbE9VeFMrbNZl2GzoRHRrrWy+hG8ZPg13xb+XS+CIAAAAASUVORK5CYII=">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content" class="container"></div>
|
||||
<script src="/public/script.js"></script>
|
||||
<div class="container">
|
||||
<h2 class="bot">Playlists</h2>
|
||||
|
||||
<section>
|
||||
<div id="items" class="item-container">
|
||||
{{ range .Playlists }}
|
||||
<div class="item" data-id="{{ .Id }}">{{ .Title }}</div>
|
||||
{{ end}}
|
||||
<div class="item locked stop" data-id="reset">Stop</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<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>
|
||||
|
||||
<div id="info">
|
||||
<a id="link" style="text-decoration: none;">
|
||||
<span id="channel"></span>
|
||||
<span> - </span>
|
||||
<span id="title"></span>
|
||||
</a>
|
||||
|
||||
<div class="controls">
|
||||
<input id="prev" name="prev" type="button" value="prev">
|
||||
<span id="time"></span>
|
||||
<input id="next" name="next" type="button" value="next">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2 class="bot">Ambiance</h2>
|
||||
|
||||
<section>
|
||||
<div id="ambiance" class="item-container">
|
||||
{{ range .Ambiance }}
|
||||
<div class="item drag" data-id="{{ .Id }}">{{ .Title }}</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>
|
||||
|
||||
<section>
|
||||
<div id="volume_playlist" class="input-container">
|
||||
<label for="playlist-volume">Playlist</label>
|
||||
<input type="range" id="playlist-volume" min="-6" max="1" step="0.1">
|
||||
<input id="playlist-volume-number" type="number" min="-6" max="1" step="0.1" style="width:50px" />
|
||||
</div>
|
||||
<div id="volume_ambiance" class="input-container">
|
||||
<label for="ambiance-volume">Ambiance</label>
|
||||
<input type="range" id="ambiance-volume" min="-4" max="4" step="0.1">
|
||||
<input id="ambiance-volume-number" type="number" min="-4" max="4" step="0.1" style="width:50px" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- The Modal -->
|
||||
<div id="waiting" class="modal">
|
||||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<p>Waiting for bot..</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/public/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,33 +1,7 @@
|
|||
# audio_output {
|
||||
# type "fifo"
|
||||
# name "fifo-output"
|
||||
# path "{{ .fifo }}"
|
||||
# format "48000:16:2"
|
||||
# enabled "yes"
|
||||
#}
|
||||
audio_output {
|
||||
type "snapcast"
|
||||
name "playlist"
|
||||
bind_to_address "0.0.0.0"
|
||||
port "{{ .playlist }}"
|
||||
zeroconf "no"
|
||||
format "48000:16:2"
|
||||
enabled "yes"
|
||||
always_on "yes"
|
||||
}
|
||||
audio_output {
|
||||
type "snapcast"
|
||||
name "ambiance"
|
||||
bind_to_address "0.0.0.0"
|
||||
port "{{ .ambiance }}"
|
||||
zeroconf "no"
|
||||
format "48000:16:2"
|
||||
enabled "yes"
|
||||
always_on "yes"
|
||||
}
|
||||
input {
|
||||
plugin "curl"
|
||||
verbose "yes"
|
||||
type "fifo"
|
||||
name "fifo-output"
|
||||
path "{{ .fifo }}"
|
||||
}
|
||||
decoder {
|
||||
plugin "wildmidi"
|
||||
|
@ -47,6 +21,6 @@ resampler {
|
|||
}
|
||||
|
||||
volume_normalization "yes"
|
||||
# audio_output_format "48000:16:2"
|
||||
audio_output_format "48000:16:2"
|
||||
bind_to_address "{{ .sock }}"
|
||||
pid_file "{{ .pid }}"
|
||||
|
|
68
ws.go
68
ws.go
|
@ -12,21 +12,18 @@ import (
|
|||
"github.com/kataras/go-events"
|
||||
)
|
||||
|
||||
type Websocket struct {
|
||||
sync.Mutex
|
||||
|
||||
clients *bcast.Group
|
||||
}
|
||||
|
||||
var ws *Websocket
|
||||
|
||||
func init() {
|
||||
log.Println("ws.go loading..")
|
||||
ws = new(Websocket)
|
||||
go ws_clients.Broadcast(0)
|
||||
ws_msg = make(chan interface{})
|
||||
|
||||
ws.clients = bcast.NewGroup()
|
||||
|
||||
go ws.clients.Broadcast(0)
|
||||
go func() {
|
||||
var msg interface{}
|
||||
for {
|
||||
msg = <-ws_msg
|
||||
ws_clients.Send(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("ws.go done.")
|
||||
}
|
||||
|
@ -36,19 +33,12 @@ type WSmsg struct {
|
|||
Payload json.RawMessage
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Event string `json:"event"`
|
||||
Payload any `json:"payload,omitempty"`
|
||||
}
|
||||
var ws_clients = bcast.NewGroup()
|
||||
var ws_msg chan interface{}
|
||||
var WSMutex = &sync.Mutex{}
|
||||
|
||||
func (ws *Websocket) SendEvent(e Event) {
|
||||
ws.Lock()
|
||||
ws.clients.Send(e)
|
||||
ws.Unlock()
|
||||
}
|
||||
|
||||
func (ws *Websocket) join(c *websocket.Conn) error {
|
||||
memb := ws.clients.Join()
|
||||
func handleWS(c *websocket.Conn) error {
|
||||
memb := ws_clients.Join()
|
||||
defer memb.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
@ -74,27 +64,29 @@ func (ws *Websocket) join(c *websocket.Conn) error {
|
|||
return nil
|
||||
})
|
||||
|
||||
msg, err := app.songInfoEvent("song_info")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := songInfoEvent("song_info")
|
||||
|
||||
vol := Event{"volume", map[string]float64{
|
||||
"playlist": pl_volume.Volume,
|
||||
"ambiance": amb_volume.Volume,
|
||||
}}
|
||||
vol := make(map[string]interface{})
|
||||
volout := make(map[string]float64)
|
||||
vol["event"] = "volume"
|
||||
volout["playlist"] = pl_volume.Volume
|
||||
volout["ambiance"] = amb_volume.Volume
|
||||
vol["payload"] = volout
|
||||
|
||||
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
c.WriteJSON(msg)
|
||||
c.WriteJSON(vol)
|
||||
|
||||
if app.ambiance.Len() > 0 {
|
||||
msg := Event{"ambiance_play", map[string]string{
|
||||
"id": app.curamb.Id,
|
||||
}}
|
||||
if amb_mixer.Len() > 0 {
|
||||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_play"
|
||||
out["id"] = amb_curr.Id
|
||||
msg["payload"] = out
|
||||
c.WriteJSON(msg)
|
||||
} else {
|
||||
msg := Event{"ambiance_stop", nil}
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_stop"
|
||||
c.WriteJSON(msg)
|
||||
}
|
||||
|
||||
|
@ -105,6 +97,6 @@ func (ws *Websocket) join(c *websocket.Conn) error {
|
|||
return err
|
||||
}
|
||||
|
||||
app.events.Emit(events.EventName(msg.Event), msg.Payload, memb)
|
||||
ev.Emit(events.EventName(msg.Event), msg.Payload)
|
||||
}
|
||||
}
|
||||
|
|
142
youtube.go
142
youtube.go
|
@ -2,81 +2,123 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"dndmusicbot/youtube"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/grafov/m3u8"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
mrand "math/rand"
|
||||
|
||||
"github.com/sosodev/duration"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
)
|
||||
|
||||
var yt_svc *youtube.Service
|
||||
|
||||
func init() {
|
||||
log.Println("youtube.go loading..")
|
||||
|
||||
app.youtube = youtube.New()
|
||||
var err error
|
||||
|
||||
apikey := config.GetString("youtube.apikey")
|
||||
|
||||
yt_svc, err = youtube.NewService(context.Background(), option.WithAPIKey(apikey))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("youtube.go done.")
|
||||
}
|
||||
|
||||
func ProxyTube(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
os.MkdirAll("cache", 0755)
|
||||
_, data, err := app.cache.GetOrCreateBytes("youtube."+p.ByName("id"), func() (out []byte, err error) {
|
||||
vinfo, err := app.youtube.GetVideoFromID(p.ByName("id"))
|
||||
func ShufflePlaylist(list []string) ([]string, error) {
|
||||
seedb := make([]byte, 32)
|
||||
_, err := rand.Read(seedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seed := binary.BigEndian.Uint64(seedb)
|
||||
mrand.Seed(int64(seed))
|
||||
mrand.Shuffle(len(list), func(i, j int) { list[i], list[j] = list[j], list[i] })
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type VideoInfo struct {
|
||||
Title string
|
||||
Channel string
|
||||
Len time.Duration
|
||||
Uri string
|
||||
}
|
||||
|
||||
func YTVideo(vid string) (out VideoInfo, err error) {
|
||||
_, r, err := cache.GetOrCreate(vid+".videoinfo", func() (io.ReadCloser, error) {
|
||||
call := yt_svc.Videos.List([]string{"snippet", "contentDetails"})
|
||||
call.MaxResults(1)
|
||||
call.Id(vid)
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
uri := vinfo.GetHLSPlaylist("234")
|
||||
|
||||
resp, err := http.Get(uri)
|
||||
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 {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
pl, _, err := m3u8.DecodeFrom(resp.Body, true)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
out := new(VideoInfo)
|
||||
out.Len = songdur.ToTimeDuration()
|
||||
out.Title = video.Snippet.Title
|
||||
out.Channel = video.Snippet.ChannelTitle
|
||||
out.Uri = vid
|
||||
|
||||
var segresp *http.Response
|
||||
mediapl := pl.(*m3u8.MediaPlaylist)
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, segment := range mediapl.GetAllSegments() {
|
||||
segresp, err = http.Get(segment.URI)
|
||||
if err != nil {
|
||||
segresp.Body.Close()
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(buf, segresp.Body)
|
||||
if err != nil {
|
||||
segresp.Body.Close()
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
json.NewEncoder(&buf).Encode(out)
|
||||
|
||||
return io.NopCloser(&buf), nil
|
||||
|
||||
return buf.Bytes(), nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
json.NewDecoder(r).Decode(&out)
|
||||
|
||||
seeker := bytes.NewReader(data)
|
||||
w.Header().Add("Content-Type", "audio/mp4")
|
||||
http.ServeContent(w, r, "youtube."+p.ByName("id"), time.Now(), seeker)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func YTPlaylist(playlist string) ([]string, error) {
|
||||
call := yt_svc.PlaylistItems.List([]string{"contentDetails"})
|
||||
pageToken := ""
|
||||
call = call.MaxResults(50)
|
||||
call = call.PlaylistId(playlist)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
var list []string
|
||||
for {
|
||||
response, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range response.Items {
|
||||
list = append(list, item.ContentDetails.VideoId)
|
||||
}
|
||||
pageToken = response.NextPageToken
|
||||
if pageToken == "" {
|
||||
break
|
||||
}
|
||||
call.PageToken(pageToken)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
package youtube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafov/m3u8"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
InnertubeClient *InnertubeClient
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type InnertubeClient struct {
|
||||
Key string `json:"-"`
|
||||
HDRClientName string `json:"-"`
|
||||
HL string `json:"hl,omitempty"`
|
||||
GL string `json:"gl,omitempty"`
|
||||
ClientName string `json:"clientName,omitempty"`
|
||||
ClientVersion string `json:"clientVersion,omitempty"`
|
||||
DeviceModel string `json:"deviceModel,omitempty"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
TimeZone string `json:"timeZone,omitempty"`
|
||||
UTCOffsetMins int64 `json:"utcOffsetMinutes,omitempty"`
|
||||
}
|
||||
|
||||
type innertubeRequest struct {
|
||||
VideoID string `json:"videoId,omitempty"`
|
||||
BrowseID string `json:"browseId,omitempty"`
|
||||
Continuation string `json:"continuation,omitempty"`
|
||||
Context struct {
|
||||
Client InnertubeClient `json:"client"`
|
||||
} `json:"context"`
|
||||
PlaybackContext struct {
|
||||
ContentPlaybackContext struct {
|
||||
HTML5Preference string `json:"html5Preference"`
|
||||
} `json:"contentPlaybackContext"`
|
||||
} `json:"playbackContext,omitempty"`
|
||||
ContentCheckOK bool `json:"contentCheckOk,omitempty"`
|
||||
RacyCheckOk bool `json:"racyCheckOk,omitempty"`
|
||||
Params string `json:"params"`
|
||||
}
|
||||
|
||||
var (
|
||||
IOSClient = InnertubeClient{
|
||||
Key: "AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI",
|
||||
HDRClientName: "5",
|
||||
HL: "en",
|
||||
GL: "US",
|
||||
ClientName: "IOS",
|
||||
ClientVersion: "17.33.2",
|
||||
DeviceModel: "iPhone14,3",
|
||||
UserAgent: "com.google.ios.youtube/17.33.2 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
|
||||
TimeZone: "UTC",
|
||||
UTCOffsetMins: 0,
|
||||
}
|
||||
)
|
||||
|
||||
var DefaultClient = IOSClient
|
||||
|
||||
func New() *Client {
|
||||
client := new(Client)
|
||||
|
||||
client.HTTPClient = &http.Client{}
|
||||
client.InnertubeClient = &DefaultClient
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (c Client) GetVideoFromID(id string) (out Video, err error) {
|
||||
uri, _ := url.Parse("https://www.youtube.com/youtubei/v1/player")
|
||||
q := uri.Query()
|
||||
q.Add("key", c.InnertubeClient.Key)
|
||||
uri.RawQuery = q.Encode()
|
||||
|
||||
reqData := new(innertubeRequest)
|
||||
reqData.Context.Client = *c.InnertubeClient
|
||||
reqData.VideoID = id
|
||||
reqData.ContentCheckOK = true
|
||||
reqData.RacyCheckOk = true
|
||||
reqData.Params = "CgIQBg=="
|
||||
reqData.PlaybackContext.ContentPlaybackContext.HTML5Preference = "HTML5_PREF_WANTS"
|
||||
|
||||
reqBody, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
payload := bytes.NewReader(reqBody)
|
||||
req, err := http.NewRequest("POST", uri.String(), payload)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
req.Header.Add("User-Agent", c.InnertubeClient.UserAgent)
|
||||
req.Header.Add("X-YouTube-Client-Name", c.InnertubeClient.HDRClientName)
|
||||
req.Header.Add("X-YouTube-Client-Version", c.InnertubeClient.ClientVersion)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
json.NewDecoder(res.Body).Decode(&out)
|
||||
|
||||
return
|
||||
}
|
||||
func (v Video) GetUrlByItag(itagNo int64) string {
|
||||
for _, format := range v.StreamingData.AdaptiveFormats {
|
||||
if format.Itag == itagNo {
|
||||
return format.URL
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v Video) GetHLSPlaylist(itag string) (out string) {
|
||||
resp, err := http.Get(v.StreamingData.HlsManifestURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
p, _, err := m3u8.DecodeFrom(resp.Body, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pl := p.(*m3u8.MasterPlaylist)
|
||||
for _, variant := range pl.Variants {
|
||||
if len(variant.Alternatives) > 0 {
|
||||
for _, a := range variant.Alternatives {
|
||||
if a.GroupId == itag {
|
||||
return a.URI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package youtube
|
||||
|
||||
type Video struct {
|
||||
StreamingData struct {
|
||||
AdaptiveFormats []struct {
|
||||
ApproxDurationMs string `json:"approxDurationMs"`
|
||||
AudioChannels int64 `json:"audioChannels"`
|
||||
AudioQuality string `json:"audioQuality"`
|
||||
AudioSampleRate string `json:"audioSampleRate"`
|
||||
AverageBitrate int64 `json:"averageBitrate"`
|
||||
Bitrate int64 `json:"bitrate"`
|
||||
ColorInfo struct {
|
||||
MatrixCoefficients string `json:"matrixCoefficients"`
|
||||
Primaries string `json:"primaries"`
|
||||
TransferCharacteristics string `json:"transferCharacteristics"`
|
||||
} `json:"colorInfo"`
|
||||
ContentLength string `json:"contentLength"`
|
||||
Fps int64 `json:"fps"`
|
||||
Height int64 `json:"height"`
|
||||
HighReplication bool `json:"highReplication"`
|
||||
IndexRange struct {
|
||||
End string `json:"end"`
|
||||
Start string `json:"start"`
|
||||
} `json:"indexRange"`
|
||||
InitRange struct {
|
||||
End string `json:"end"`
|
||||
Start string `json:"start"`
|
||||
} `json:"initRange"`
|
||||
Itag int64 `json:"itag"`
|
||||
LastModified string `json:"lastModified"`
|
||||
LoudnessDB float64 `json:"loudnessDb"`
|
||||
MimeType string `json:"mimeType"`
|
||||
ProjectionType string `json:"projectionType"`
|
||||
Quality string `json:"quality"`
|
||||
QualityLabel string `json:"qualityLabel"`
|
||||
URL string `json:"url"`
|
||||
Width int64 `json:"width"`
|
||||
} `json:"adaptiveFormats"`
|
||||
AspectRatio float64 `json:"aspectRatio"`
|
||||
ExpiresInSeconds string `json:"expiresInSeconds"`
|
||||
HlsManifestURL string `json:"hlsManifestUrl"`
|
||||
ServerAbrStreamingURL string `json:"serverAbrStreamingUrl"`
|
||||
} `json:"streamingData"`
|
||||
VideoDetails struct {
|
||||
AllowRatings bool `json:"allowRatings"`
|
||||
Author string `json:"author"`
|
||||
ChannelID string `json:"channelId"`
|
||||
IsCrawlable bool `json:"isCrawlable"`
|
||||
IsLiveContent bool `json:"isLiveContent"`
|
||||
IsOwnerViewing bool `json:"isOwnerViewing"`
|
||||
IsPrivate bool `json:"isPrivate"`
|
||||
IsUnpluggedCorpus bool `json:"isUnpluggedCorpus"`
|
||||
Keywords []string `json:"keywords"`
|
||||
LengthSeconds string `json:"lengthSeconds"`
|
||||
ShortDescription string `json:"shortDescription"`
|
||||
Thumbnail struct {
|
||||
Thumbnails []struct {
|
||||
Height int64 `json:"height"`
|
||||
URL string `json:"url"`
|
||||
Width int64 `json:"width"`
|
||||
} `json:"thumbnails"`
|
||||
} `json:"thumbnail"`
|
||||
Title string `json:"title"`
|
||||
VideoID string `json:"videoId"`
|
||||
ViewCount string `json:"viewCount"`
|
||||
} `json:"videoDetails"`
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package youtube
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
client := New()
|
||||
video, err := client.GetVideoFromID("LHsGz91Ivwg")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(video.GetHLSPlaylist("234"))
|
||||
}
|
126
ytdl.go
126
ytdl.go
|
@ -3,41 +3,17 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
var prate = rate.Sometimes{Interval: 1 * time.Second}
|
||||
var yturl = "https://youtu.be/%s"
|
||||
|
||||
// var yturl = "https://youtu.be/%s"
|
||||
|
||||
func YTUrl(uri string) (vid string, err error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch u.Host {
|
||||
case "youtu.be":
|
||||
vid = u.Path[1:]
|
||||
case "m.youtube.com":
|
||||
fallthrough
|
||||
case "youtube.com":
|
||||
fallthrough
|
||||
case "www.youtube.com":
|
||||
vid = u.Query().Get("v")
|
||||
}
|
||||
|
||||
if vid == "" {
|
||||
return vid, fmt.Errorf("unable to parse vid")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
func NewYTdlUrl(vid string) ([]byte, error) {
|
||||
func NewYTdl(vid string) ([]byte, error) {
|
||||
ytdl := config.GetString("youtube.ytdl")
|
||||
yt := exec.Command(
|
||||
ytdl,
|
||||
|
@ -48,7 +24,7 @@ func NewYTdlUrl(vid string) ([]byte, error) {
|
|||
"--ignore-errors",
|
||||
"--newline",
|
||||
"--restrict-filenames",
|
||||
"-f", "234",
|
||||
"-f", "251",
|
||||
"--get-url",
|
||||
)
|
||||
|
||||
|
@ -62,96 +38,22 @@ func NewYTdlUrl(vid string) ([]byte, error) {
|
|||
return uri[:len(uri)-1], nil
|
||||
}
|
||||
|
||||
func NewYTdl(vid string) ([]byte, error) {
|
||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_ytdlp_XXXXXXXXXXXX.m4a").Output()
|
||||
func YTUrl(uri string) (vid string, err error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpfile = tmpfile[:len(tmpfile)-1]
|
||||
|
||||
ytdl := config.GetString("youtube.ytdl")
|
||||
yt := exec.Command(
|
||||
ytdl,
|
||||
fmt.Sprintf(yturl, vid),
|
||||
"-q",
|
||||
"--cookies", "./cookies.txt",
|
||||
"--no-call-home",
|
||||
"--no-cache-dir",
|
||||
"--ignore-errors",
|
||||
"--newline",
|
||||
"--restrict-filenames",
|
||||
"--force-overwrites",
|
||||
"--progress",
|
||||
"--progress-template", "download:{ \"dl_bytes\": \"%(progress.downloaded_bytes)s\", \"total_bytes\": \"%(progress.total_bytes)s\" }",
|
||||
"-f", "251",
|
||||
"-o", string(tmpfile),
|
||||
)
|
||||
|
||||
yt.Stderr = os.Stderr
|
||||
ytprogress, err := yt.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch u.Host {
|
||||
case "youtu.be":
|
||||
vid = u.Path[1:]
|
||||
case "youtube.com":
|
||||
vid = u.Query().Get("v")
|
||||
}
|
||||
|
||||
err = yt.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile))
|
||||
msg := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_download_start"
|
||||
data := make(map[string]string)
|
||||
data["name"] = fmt.Sprintf(yturl, vid)
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
|
||||
msg = make(map[string]interface{})
|
||||
msg["event"] = "ambiance_download_progress"
|
||||
data = make(map[string]string)
|
||||
data["name"] = fmt.Sprintf(yturl, vid)
|
||||
|
||||
scanner := bufio.NewScanner(ytprogress)
|
||||
|
||||
for scanner.Scan() {
|
||||
err := json.Unmarshal(scanner.Bytes(), &data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
dl, _ := strconv.ParseFloat(data["dl_bytes"], 64)
|
||||
total, _ := strconv.ParseFloat(data["total_bytes"], 64)
|
||||
percent := math.Floor((dl / total) * 100)
|
||||
data["percent"] = strconv.FormatInt(int64(percent), 10)
|
||||
|
||||
prate.Do(func() {
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
})
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yt.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg = make(map[string]interface{})
|
||||
msg["event"] = "ambiance_download_complete"
|
||||
data = make(map[string]string)
|
||||
data["name"] = fmt.Sprintf(yturl, vid)
|
||||
msg["payload"] = data
|
||||
ws_msg <- msg
|
||||
|
||||
return tmpfile, nil
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
func DownloadAmbiance(uri string, name string) error {
|
||||
ytdl := config.GetString("youtube.ytdl")
|
||||
|
|
Loading…
Reference in New Issue