Compare commits

..

2 Commits

Author SHA1 Message Date
Stein Ivar Berghei ef2d568a6e fixup! Initial commit 2022-11-18 22:05:41 +01:00
Stein Ivar Berghei d25655ef4a Add go module files 2022-11-18 22:04:36 +01:00
12 changed files with 137 additions and 54 deletions

4
.gitignore vendored
View File

@ -5,6 +5,8 @@ oauth2-proxy
test/* test/*
cookies.txt cookies.txt
config config
css/*.min.css
js/*.min.js
bin bin
ambiance ambiance/*.mp3

12
bot.go
View File

@ -27,18 +27,6 @@ const (
maxBytes int = (frameSize * 2) * 2 // max size of opus data maxBytes int = (frameSize * 2) * 2 // max size of opus data
) )
/*
var (
token = "MTA0MDQwNTk3MDc3MTI1NTM1OQ.Gyg_y7.DqK6Tudd-iQVvgItkWvwGEwQZ7tY4qJBaBqTuQ"
channel = "675319944853979140"
server = "675319944853979136"
//channel = "1029091047902547980"
//server = "231872254525440000"
)
*/
var ( var (
app = new(App) app = new(App)
config = viper.GetViper() config = viper.GetViper()

View File

@ -1 +0,0 @@
@import url(https://fonts.googleapis.com/css?family=Inconsolata);@import url(https://fonts.googleapis.com/css?family=PT+Sans);@import url(https://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700);img,legend{border:0}html,pre{color:#839496}pre,pre code{background-color:#002b36}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}body,figure{margin:0}a:focus{outline:dotted thin}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap;word-wrap:break-word;border:1pt solid #586e75;padding:1em;box-shadow:5pt 5pt 8pt #073642}.tag,code,html{background-color:#073642}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;font-family:'PT Sans',sans-serif;margin:1em}code,pre{font-family:Inconsolata,sans-serif}h1,h2,h3,h4,h5,h6{font-family:'PT Sans Narrow',sans-serif;font-weight:700}code{padding:2px}a{color:#b58900}.tag,h1{color:#d33682}a:hover,a:visited{color:#cb4b16}h1{font-size:2.8em}h2,h3,h4,h5,h6{color:#859900}h2{font-size:2.4em}h3{font-size:1.8em}h4{font-size:1.4em}h5{font-size:1.3em}h6{font-size:1.15em}.tag{padding:0 .2em;-webkit-border-radius:0.35em;-moz-border-radius:.35em;border-radius:.35em}.ACTIVE,.NEXT,.TODO{-webkit-border-radius:0.2em;-moz-border-radius:.2em}.done,.next,.todo{color:#002b36;background-color:#dc322f;padding:0 .2em}.TODO{border-radius:.2em;background-color:#2aa198}.ACTIVE,.NEXT{border-radius:.2em;background-color:#268bd2}.CANCELLED,.DONE,.WAITING{-webkit-border-radius:0.2em;-moz-border-radius:.2em}.CANCELLED,.DONE{border-radius:.2em;background-color:#859900}.WAITING{border-radius:.2em;background-color:#cb4b16}.HOLD,.NOTE{-webkit-border-radius:0.2em;-moz-border-radius:.2em;border-radius:.2em;background-color:#d33682}

2
db.go
View File

@ -16,7 +16,7 @@ func init() {
log.Println("db.go loading..") log.Println("db.go loading..")
var err error var err error
app.db, err = pgx.Connect(context.Background(), connstring) app.db, err = pgx.Connect(context.Background(), config.GetString("db.connstring"))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -3,7 +3,9 @@ package main
import ( import (
"context" "context"
"dndmusicbot/ffmpeg" "dndmusicbot/ffmpeg"
"dndmusicbot/loop"
discordspeaker "dndmusicbot/speaker" discordspeaker "dndmusicbot/speaker"
"dndmusicbot/ytdl"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
@ -40,8 +42,10 @@ func init() {
app.events.On("preload_song", app.preloadSong) app.events.On("preload_song", app.preloadSong)
app.events.On("song_over", app.songOver) app.events.On("song_over", app.songOver)
//app.events.On("song_position", app.songPosition) //app.events.On("song_position", app.songPosition)
app.events.On("ambiance_play", app.ambiancePlay) app.events.On("ambiance_play", app.ambiancePlay)
app.events.On("ambiance_stop", app.ambianceStop) app.events.On("ambiance_stop", app.ambianceStop)
app.events.On("ambiance_add", app.ambianceAdd)
app.events.On("stop", app.stop) app.events.On("stop", app.stop)
app.events.On("next", app.nextSong) app.events.On("next", app.nextSong)
@ -127,7 +131,9 @@ func (app *App) ambiancePlay(payload ...interface{}) {
if app.ambiance.IsPlaying() { if app.ambiance.IsPlaying() {
app.ambiance.Reset() app.ambiance.Reset()
} }
app.ambiance.Add(play)
loop := loop.Loop(-1, play)
app.ambiance.Add(loop)
discordspeaker.Unlock() discordspeaker.Unlock()
msg := make(map[string]interface{}) msg := make(map[string]interface{})
@ -153,6 +159,48 @@ func (app *App) ambianceStop(payload ...interface{}) {
} }
func (app *App) ambianceAdd(payload ...interface{}) {
if !(len(payload) > 0) {
log.Println("addPlaylist called without a payload.")
return
}
log.Println("ambiance_add event received")
var data map[string]string
switch js := payload[0].(type) {
case json.RawMessage:
json.Unmarshal(js, &data)
default:
log.Println("newPlaylist called with invalid payload.")
return
}
amburl, ok := data["url"]
if !ok {
log.Println("addPlaylist without url")
return
}
ambtitle, ok := data["title"]
if !ok {
log.Println("addPlaylist without title")
return
}
err := ytdl.DownloadAmbiance(amburl, ambtitle)
if err != nil {
log.Println(err)
return
}
msg := make(map[string]interface{})
out := make(map[string]interface{})
msg["event"] = "ambiance_add"
out["type"] = ambtitle
msg["payload"] = out
ws_msg <- msg
}
func (app *App) songPosition(payload ...interface{}) { func (app *App) songPosition(payload ...interface{}) {
if !app.queue.IsPlaying() { if !app.queue.IsPlaying() {
return return
@ -256,16 +304,6 @@ func (app *App) nextSong(payload ...interface{}) {
return return
} }
for {
time.Sleep(1 * time.Second)
if app.queue.QLen() == 0 {
log.Println("retrying...")
song = app.GetSong(app.active)
} else {
break
}
}
discordspeaker.Lock() discordspeaker.Lock()
app.queue.Reset() app.queue.Reset()
app.queue.Add(f) app.queue.Add(f)

View File

@ -24,6 +24,7 @@ type FFmpeg struct {
PMutex *sync.RWMutex PMutex *sync.RWMutex
ProcessState *os.ProcessState ProcessState *os.ProcessState
err chan error err chan error
fb chan bool
} }
func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error) { func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error) {
@ -42,6 +43,7 @@ func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error)
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
RETRY:
ff = new(FFmpeg) ff = new(FFmpeg)
ff.PMutex = &sync.RWMutex{} ff.PMutex = &sync.RWMutex{}
ff.Len = yt.Len ff.Len = yt.Len
@ -53,6 +55,7 @@ func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error)
ff.Cmd = exec.CommandContext( ff.Cmd = exec.CommandContext(
ctx, ctx,
"ffmpeg", "ffmpeg",
"-xerror",
"-i", yt.Url, "-i", yt.Url,
"-f", "s16le", "-f", "s16le",
"-v", "error", "-v", "error",
@ -71,18 +74,23 @@ func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error)
ff.Out = bytes.NewBuffer(make([]byte, 43920)) ff.Out = bytes.NewBuffer(make([]byte, 43920))
ff.Cmd.Stdout = ff.Out ff.Cmd.Stdout = ff.Out
ff.Start() exitctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return
}
func (ff *FFmpeg) Start() {
go func() { go func() {
ff.Cmd.Start() ff.Cmd.Start()
ff.err <- ff.Cmd.Wait() ff.err <- ff.Cmd.Wait()
}() }()
ff.Started = true select {
case <-exitctx.Done():
ff.Started = true
case <-ff.err:
log.Println("ffmpeg exited early, retry...")
goto RETRY
}
return
} }
func (ff *FFmpeg) Close() error { func (ff *FFmpeg) Close() error {

2
go.mod
View File

@ -2,8 +2,6 @@ module dndmusicbot
go 1.19 go 1.19
replace github.com/3d0c/gmf => /home/steino/dev/go/gmf
require ( require (
github.com/bwmarrin/discordgo v0.26.1 github.com/bwmarrin/discordgo v0.26.1
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8 github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8

2
js/Sortable.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});

View File

@ -1,4 +1,4 @@
window.onload = function () { window.onload = function() {
const ws = new ReconnectingWebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws"); const ws = new ReconnectingWebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws");
const items = document.querySelector("#items"); const items = document.querySelector("#items");
@ -24,17 +24,18 @@ window.onload = function () {
group: "dndmusicbot-playlists", group: "dndmusicbot-playlists",
filter: ".locked", filter: ".locked",
onMove: (e) => { onMove: (e) => {
if (e.related) { if (e.related)
return !e.related.classList.contains('locked'); {
} return !e.related.classList.contains('locked');
}
}, },
invertSwap: true, invertSwap: true,
store: { store: {
get: function (sortable) { get: function(sortable) {
var order = localStorage.getItem(sortable.options.group.name); var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : []; return order ? order.split('|') : [];
}, },
set: function (sortable) { set: function(sortable) {
var order = sortable.toArray(); var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|')); localStorage.setItem(sortable.options.group.name, order.join('|'));
} }
@ -45,17 +46,18 @@ window.onload = function () {
group: "dndmusicbot-ambiance", group: "dndmusicbot-ambiance",
filter: ".locked", filter: ".locked",
onMove: (e) => { onMove: (e) => {
if (e.related) { if (e.related)
return !e.related.classList.contains('locked'); {
} return !e.related.classList.contains('locked');
}
}, },
invertSwap: true, invertSwap: true,
store: { store: {
get: function (sortable) { get: function(sortable) {
var order = localStorage.getItem(sortable.options.group.name); var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : []; return order ? order.split('|') : [];
}, },
set: function (sortable) { set: function(sortable) {
var order = sortable.toArray(); var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|')); localStorage.setItem(sortable.options.group.name, order.join('|'));
} }
@ -120,12 +122,32 @@ window.onload = function () {
} }
} }
document.querySelector("#addambiance").addEventListener("click", (e) => {
e.preventDefault()
output.innerText = ""
var title = document.querySelector("#inputambiance > input[name='title']")
var url = document.querySelector("#inputambiance > input[name='url']")
if (title.value == "" || url.value == "") {
output.innerText = "Title or Url is empty!"
return
}
ws.send(JSON.stringify({
"event": "ambiance_add",
"payload": {
"title": title.value,
"url": url.value
}
}))
})
submit.addEventListener("click", (e) => { submit.addEventListener("click", (e) => {
e.preventDefault() e.preventDefault()
output.innerText = "" output.innerText = ""
var title = document.querySelector(".container2 input[name='title'") var title = document.querySelector("#inputplaylist > input[name='title']")
var url = document.querySelector(".container2 input[name='url'") var url = document.querySelector("#inputplaylist > input[name='url']")
if (title.value == "" || url.value == "") { if (title.value == "" || url.value == "") {
output.innerText = "Title or Url is empty!" output.innerText = "Title or Url is empty!"
return return
@ -138,6 +160,9 @@ window.onload = function () {
"url": url.value "url": url.value
} }
})) }))
title.innerText = ""
url.innerText = ""
}) })
document.querySelectorAll("#items").forEach(item => item.addEventListener("click", (e) => { document.querySelectorAll("#items").forEach(item => item.addEventListener("click", (e) => {

View File

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>D&D Music Bot!</title> <title>D&D Music Bot!</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/solarized-dark.min.css"> <link rel="stylesheet" href="/css/solarized-dark.min.css">
<link rel="stylesheet" href="/css/style.css">
</head> </head>
<body> <body>
<div><h2 class="bot">Playlists</h2></div> <div><h2 class="bot">Playlists</h2></div>
@ -16,17 +16,17 @@
{{ end}} {{ end}}
<div class="item locked" data-id="reset">Stop</div> <div class="item locked" data-id="reset">Stop</div>
</div> </div>
<div class="container2"> <div id="inputplaylist" class="container2">
<input name="title" type="text" placeholder="Enter name.."> <input name="title" type="text" placeholder="Enter name..">
<input name="url" type="text" placeholder="https://youtube.com/playlist?list=..."> <input name="url" type="text" placeholder="https://youtube.com/playlist?list=...">
<input id="addplaylist" name="submit" value="Add" type="submit"> <input id="addplaylist" name="submit" value="Add" type="submit">
<input id="prev" name="prev" type="button" value="prev">
<input id="next" name="next" type="button" value="next">
</div> </div>
<div class="container2"> <div class="container2">
<p id="output"></p> <p id="output"></p>
</div> </div>
<div id="info" class="container2"> <div id="info" class="container2">
<input id="prev" name="prev" type="button" value="prev">
<input id="next" name="next" type="button" value="next">
<a id="link" style="text-decoration: none;"> <a id="link" style="text-decoration: none;">
<span id="channel"></span> <span id="channel"></span>
<span> - </span> <span> - </span>
@ -41,6 +41,11 @@
{{ end}} {{ end}}
<div class="item locked" data-id="reset">Stop</div> <div class="item locked" data-id="reset">Stop</div>
</div> </div>
<div id="inputambiance" class="container2">
<input name="title" type="text" placeholder="Enter name..">
<input name="url" type="text" placeholder="Enter url...">
<input id="addambiance" name="submit" value="Add" type="submit">
</div>
<!-- The Modal --> <!-- The Modal -->
<div id="waiting" class="modal"> <div id="waiting" class="modal">
<!-- Modal content --> <!-- Modal content -->

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath"
"time" "time"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -45,3 +46,25 @@ func NewYTdl(uri string) (*YTdl, error) {
return &YTdl{title, geturl, channel, duration}, nil return &YTdl{title, geturl, channel, duration}, nil
} }
func DownloadAmbiance(uri string, name string) error {
err := exec.Command(
"./bin/yt-dlp_linux",
uri,
"-x",
"--audio-format", "mp3",
"--postprocessor-args", "-ar 48000 -ac 2",
"--cookies", "./cookies.txt",
"--no-call-home",
"--no-cache-dir",
"--restrict-filenames",
"-f", "140",
"-o", filepath.Join("./ambiance/", name+".mp3"),
).Run()
if err != nil {
return nil
}
return nil
}