all repos — disgord @ 788b481ce887db1dccfc707bc18e508405fdd809

A simple Discord bot in Go.

add youtube playback
Marco Andronaco andronacomarco@gmail.com
Fri, 04 Oct 2024 16:14:18 +0200
commit

788b481ce887db1dccfc707bc18e508405fdd809

parent

41b487beb8025d82e480ed8899f028ac2d381a06

M .vscode/launch.json.vscode/launch.json

@@ -9,7 +9,7 @@ "name": "Launch Package",

"type": "go", "request": "launch", "mode": "auto", - "program": "${fileDirname}" + "program": "." }, ] }
M go.modgo.mod

@@ -3,22 +3,23 @@

go 1.23.1 require ( + github.com/ClintonCollins/dca v1.0.4 github.com/bwmarrin/discordgo v0.28.1 - github.com/joho/godotenv v1.5.1 + github.com/kkdai/youtube/v2 v2.10.1 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 ) require ( github.com/bitly/go-simplejson v0.5.1 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect - github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 // indirect - github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect - github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect - github.com/gorilla/websocket v1.4.2 // indirect - github.com/kkdai/youtube/v2 v2.10.1 // indirect - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect + github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect ) replace github.com/BiRabittoh/disgord/src => ./src
M go.sumgo.sum

@@ -1,3 +1,5 @@

+github.com/ClintonCollins/dca v1.0.4 h1:U4G3tR5G25gma6omYH74e2RGIe+mW4SSqAN0NIWMVsM= +github.com/ClintonCollins/dca v1.0.4/go.mod h1:WlhWjtkTbNQvDdWUIEc1A97eCrNyKPXguNldmTlbKTg= 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/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=

@@ -6,25 +8,38 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=

github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM= github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 h1:Ux9RXuPQmTB4C1MKagNLme0krvq8ulewfor+ORO/QL4= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= 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/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/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d h1:Jaz2JzpQaQXyET0AjLBXShrthbpqMkhGiEfkcQAiAUs= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757 h1:Kyv+zTfWIGRNaz/4+lS+CxvuKVZSKFz/6G8E3BKKBRs= +github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757/go.mod h1:cZnNmdLiLpihzgIVqiaQppi9Ts3D4qF/M45//yW35nI= github.com/kkdai/youtube/v2 v2.10.1 h1:jdPho4R7VxWoRi9Wx4ULMq4+hlzSVOXxh4Zh83f2F9M= github.com/kkdai/youtube/v2 v2.10.1/go.mod h1:qL8JZv7Q1IoDs4nnaL51o/hmITXEIvyCIXopB0oqgVM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=

@@ -33,23 +48,30 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=

github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

@@ -57,6 +79,8 @@ 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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

@@ -66,6 +90,8 @@ 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

@@ -74,4 +100,7 @@ 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
M main.gomain.go

@@ -5,6 +5,7 @@ "os"

"os/signal" "github.com/BiRabittoh/disgord/src" + g "github.com/BiRabittoh/disgord/src/globals" "github.com/BiRabittoh/disgord/src/myconfig" "github.com/BiRabittoh/disgord/src/mylog" "github.com/bwmarrin/discordgo"

@@ -13,7 +14,12 @@

var logger = mylog.NewLogger(os.Stdout, "init", mylog.DEBUG) func messageHandler(s *discordgo.Session, m *discordgo.MessageCreate) { - logger.Debug("got a message: " + m.Content) + if m.Author.ID == s.State.User.ID { + logger.Debugf("Ignoring own message: %s", m.Content) + return + } else { + logger.Debug("Got a message: " + m.Content) + } response, ok, err := src.HandleCommand(s, m) if err != nil {

@@ -39,14 +45,14 @@ logger.Infof("Logged in as %s", r.User.String())

} func main() { - logger.Info("Starting bot... Commit " + src.CommitID) + logger.Info("Starting bot... Commit " + g.CommitID) var err error - src.Config, err = myconfig.New[src.MyConfig]("config.json") + g.Config, err = myconfig.New[g.MyConfig]("config.json") if err != nil { logger.Errorf("could not load config: %s", err) } - session, err := discordgo.New("Bot " + src.Config.Values.Token) + session, err := discordgo.New("Bot " + g.Config.Values.Token) if err != nil { logger.Fatalf("could not create bot session: %s", err) }
M src/commands.gosrc/commands.go

@@ -1,30 +1,31 @@

package src import ( - "fmt" "strings" + gl "github.com/BiRabittoh/disgord/src/globals" + "github.com/BiRabittoh/disgord/src/music" + "github.com/BiRabittoh/disgord/src/shoot" "github.com/bwmarrin/discordgo" ) var ( - handlersMap map[string]BotCommand + handlersMap map[string]gl.BotCommand shortCommands = map[string]string{} ) -func (bc BotCommand) FormatHelp(command, guildID string) string { - var shortCodeStr string - if bc.ShortCode != "" { - shortCodeStr = fmt.Sprintf(" (%s)", formatCommand(bc.ShortCode, guildID)) - } - return fmt.Sprintf(helpFmt, formatCommand(command, guildID)+shortCodeStr, bc.Help) -} - func InitHandlers() { - handlersMap = map[string]BotCommand{ + handlersMap = map[string]gl.BotCommand{ "echo": {ShortCode: "e", Handler: handleEcho, Help: "echoes a message"}, - "shoot": {ShortCode: "sh", Handler: handleShoot, Help: "shoots a random user in your voice channel"}, + "shoot": {ShortCode: "sh", Handler: shoot.HandleShoot, Help: "shoots a random user in your voice channel"}, "prefix": {Handler: handlePrefix, Help: "sets the bot's prefix for this server"}, + "play": {ShortCode: "p", Handler: music.HandlePlay, Help: "plays a song from youtube"}, + "pause": {ShortCode: "pa", Handler: music.HandlePause, Help: "pauses the current song"}, + "resume": {ShortCode: "r", Handler: music.HandleResume, Help: "resumes the current song"}, + "skip": {ShortCode: "s", Handler: music.HandleSkip, Help: "skips the current song"}, + "queue": {ShortCode: "q", Handler: music.HandleQueue, Help: "shows the current queue"}, + "clear": {ShortCode: "c", Handler: music.HandleClear, Help: "clears the current queue"}, + "leave": {ShortCode: "l", Handler: music.HandleLeave, Help: "leaves the voice channel"}, "help": {ShortCode: "h", Handler: handleHelp, Help: "shows this help message"}, }

@@ -37,7 +38,7 @@ }

} func HandleCommand(s *discordgo.Session, m *discordgo.MessageCreate) (response string, ok bool, err error) { - command, args, ok := parseUserMessage(m.Content, m.GuildID) + command, args, ok := gl.ParseUserMessage(m.Content, m.GuildID) if !ok { return }

@@ -49,7 +50,7 @@ }

botCommand, found := handlersMap[command] if !found { - response = "Unknown command: " + formatCommand(command, m.GuildID) + response = "Unknown command: " + gl.FormatCommand(command, m.GuildID) return }

@@ -63,7 +64,7 @@ }

func handlePrefix(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { if len(args) == 0 { - return "Usage: " + formatCommand("prefix <new prefix>", m.GuildID) + "." + return "Usage: " + gl.FormatCommand("prefix <new prefix>", m.GuildID) + "." } newPrefix := args[0]

@@ -71,7 +72,7 @@ if len(newPrefix) > 10 {

return "Prefix is too long." } - setPrefix(m.GuildID, newPrefix) + gl.SetPrefix(m.GuildID, newPrefix) return "Prefix set to " + newPrefix + "." }
M src/globals.gosrc/globals/globals.go

@@ -1,4 +1,4 @@

-package src +package globals import ( "os"

@@ -10,7 +10,7 @@ "github.com/kkdai/youtube/v2"

) const ( - msgError = "Something went wrong." + MsgError = "Something went wrong." helpFmt = "%s - _%s_" defaultPrefix = "$" )

@@ -20,7 +20,7 @@ CommitID string

Config *myconfig.Config[MyConfig] logger = mylog.NewLogger(os.Stdout, "main", mylog.DEBUG) - yt = youtube.Client{} + YT = youtube.Client{} ) type KeyValuePair struct {
A src/music/audio.go

@@ -0,0 +1,96 @@

+package music + +import ( + "github.com/ClintonCollins/dca" + "github.com/bwmarrin/discordgo" +) + +type Audio struct { + session *dca.EncodeSession + stream *dca.StreamingSession + Done chan error + onFinish func() + paused bool +} + +var audioEncodeOptions = &dca.EncodeOptions{ + Channels: 2, + FrameRate: 48000, + FrameDuration: 20, + Bitrate: 96, + Application: dca.AudioApplicationLowDelay, + CompressionLevel: 10, + PacketLoss: 1, + BufferedFrames: 100, + VBR: true, + StartTime: 0, + VolumeFloat: 1, + RawOutput: true, +} + +func NewAudio(url string, vc *discordgo.VoiceConnection) (as *Audio, err error) { + as = &Audio{ + session: nil, + stream: nil, + paused: false, + Done: make(chan error), + } + + as.session, err = dca.EncodeFile(url, audioEncodeOptions) + if err != nil { + return + } + + as.stream = dca.NewStream(as.session, vc, as.Done) + return +} + +func (a *Audio) Pause() { + if a.stream == nil || a.paused { + return + } + + a.stream.SetPaused(true) + a.paused = true +} + +func (a *Audio) Resume() { + if a.stream == nil || !a.paused { + return + } + + a.stream.SetPaused(false) + a.paused = false +} + +func (a *Audio) Stop() { + if a.stream == nil { + return + } + + a.session.Stop() + a.session.Cleanup() + a.stream = nil + a.session = nil +} + +func (a *Audio) Finished() (bool, error) { + return a.stream.Finished() +} + +func (a *Audio) Monitor(onFinish func()) { + go func() { + for err := range a.Done { + if err != nil { + logger.Errorf("Playback error: %v", err) + break + } + } + + a.Stop() + + if onFinish != nil { + onFinish() + } + }() +}
A src/music/commands.go

@@ -0,0 +1,158 @@

+package music + +import ( + gl "github.com/BiRabittoh/disgord/src/globals" + "github.com/bwmarrin/discordgo" +) + +func HandlePlay(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, _, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + if len(args) == 0 { + return "Please, provide a YouTube URL." + } + + voice, err := s.ChannelVoiceJoin(m.GuildID, vc, false, true) + if err != nil { + logger.Errorf("could not join voice channel: %v", err) + return gl.MsgError + } + + // Get the video information + video, err := gl.YT.GetVideo(args[0]) + if err != nil { + logger.Errorf("could not get video: %v", err) + return gl.MsgError + } + + // Get the queue for the guild + q := GetOrCreateQueue(voice) + + // Add video to the queue + q.AddVideo(video) + + return "Added to queue: " + gl.FormatVideo(video) +} + +func HandlePause(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, g, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + q := GetQueue(g.ID) + if q == nil { + return "Nothing is playing." + } + + if vc != q.VoiceChannelID() { + return "You need to be in the same voice channel to use this command." + } + + q.Pause() + + return "Paused." +} + +func HandleResume(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, g, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + q := GetQueue(g.ID) + if q == nil { + return "Nothing is playing." + } + + if vc != q.VoiceChannelID() { + return "You need to be in the same voice channel to use this command." + } + + q.Resume() + + return "Resumed." +} + +func HandleSkip(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, g, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + q := GetQueue(g.ID) + if q == nil { + return "Nothing is playing." + } + + if vc != q.VoiceChannelID() { + return "You need to be in the same voice channel to use this command." + } + + err := q.PlayNext() + if err != nil { + return "Nothing is playing." + } + + return "Skipped." +} + +func HandleQueue(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + q := GetQueue(m.GuildID) + if q == nil { + return "Nothing is playing." + } + + var out string + videos := q.Videos() + for _, v := range videos { + out += gl.FormatVideo(v) + "\n" + } + return out +} + +func HandleClear(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, g, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + q := GetQueue(g.ID) + if q == nil { + return "Nothing is playing." + } + + if vc != q.VoiceChannelID() { + return "You need to be in the same voice channel to use this command." + } + + q.Clear() + + return "Cleared." +} + +func HandleLeave(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + r, g, vc := gl.GetVoiceChannelID(s, m) + if r != "" { + return r + } + + q := GetQueue(g.ID) + if q == nil { + return "Nothing is playing." + } + + if vc != q.VoiceChannelID() { + return "You need to be in the same voice channel to use this command." + } + + err := q.Stop() + if err != nil { + return gl.MsgError + } + + return "Cleared." +}
A src/music/queue.go

@@ -0,0 +1,121 @@

+package music + +import ( + "os" + + "github.com/BiRabittoh/disgord/src/mylog" + "github.com/bwmarrin/discordgo" + "github.com/kkdai/youtube/v2" +) + +var logger = mylog.NewLogger(os.Stdin, "music", mylog.DEBUG) + +type Queue struct { + nowPlaying *youtube.Video + items []*youtube.Video + audioStream *Audio + vc *discordgo.VoiceConnection +} + +// queues stores all guild queues +var queues = map[string]*Queue{} + +// GetOrCreateQueue fetches or creates a new queue for the guild +func GetOrCreateQueue(vc *discordgo.VoiceConnection) *Queue { + q, ok := queues[vc.GuildID] + if !ok { + q = &Queue{vc: vc} + queues[vc.GuildID] = q + } + + return q +} + +// GetQueue returns either nil or the queue for the requested guild +func GetQueue(guildID string) *Queue { + q, ok := queues[guildID] + if ok { + return q + } + return nil +} + +// AddVideo adds a new video to the queue +func (q *Queue) AddVideo(video *youtube.Video) { + q.items = append(q.items, video) + if q.nowPlaying == nil { + q.PlayNext() + } +} + +// AddVideos adds a list of videos to the queue +func (q *Queue) AddVideos(videos []*youtube.Video) { + q.items = append(q.items, videos...) + if q.nowPlaying == nil { + q.PlayNext() + } +} + +// PlayNext starts playing the next video in the queue +func (q *Queue) PlayNext() (err error) { + if q.audioStream != nil { + q.audioStream.Stop() + } + + if len(q.items) == 0 { + q.nowPlaying = nil + return q.vc.Disconnect() + } + + q.nowPlaying = q.items[0] + q.items = q.items[1:] + + formats := q.nowPlaying.Formats.WithAudioChannels().Type("audio/webm") + if len(formats) == 0 { + logger.Debug("no formats with audio channels available for video " + q.nowPlaying.ID) + return q.PlayNext() + } + + q.audioStream, err = NewAudio(formats[0].URL, q.vc) + if err != nil { + return + } + + q.audioStream.Monitor(func() { q.PlayNext() }) + return +} + +// Stop stops the player and clears the queue +func (q *Queue) Stop() error { + q.Clear() + q.nowPlaying = nil + q.audioStream.Stop() + return q.vc.Disconnect() +} + +// Pause pauses the player +func (q *Queue) Pause() { + q.audioStream.Pause() +} + +// Resume resumes the player +func (q *Queue) Resume() { + q.audioStream.Resume() +} + +// Clear clears the video queue +func (q *Queue) Clear() { + q.items = []*youtube.Video{} +} + +// Videos returns all videos in the queue including the now playing one +func (q *Queue) Videos() []*youtube.Video { + if q.nowPlaying != nil { + return append([]*youtube.Video{q.nowPlaying}, q.items...) + } + return q.items +} + +func (q *Queue) VoiceChannelID() string { + return q.vc.ChannelID +}
M src/shoot.gosrc/shoot/shoot.go

@@ -1,12 +1,17 @@

-package src +package shoot import ( "fmt" + "os" "time" + gl "github.com/BiRabittoh/disgord/src/globals" + "github.com/BiRabittoh/disgord/src/mylog" "github.com/bwmarrin/discordgo" "golang.org/x/exp/rand" ) + +var logger = mylog.NewLogger(os.Stdin, "shoot", mylog.DEBUG) type Magazine struct { size uint

@@ -56,15 +61,21 @@ if ok {

return } - q = NewMagazine(Config.Values.MagazineSize) + q = NewMagazine(gl.Config.Values.MagazineSize) magazines[userID] = q return } -func handleShoot(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { +func HandleShoot(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { const bustProbability = 50 - response, guild, voiceChannelID := getVoiceChannelID(s, m) + _, err := s.Guild(m.GuildID) + if err != nil { + logger.Errorf("could not update guild: %s", err) + return gl.MsgError + } + + response, guild, voiceChannelID := gl.GetVoiceChannelID(s, m) if voiceChannelID == "" { return response }

@@ -101,7 +112,7 @@ } else {

victimID = allMembers[rand.Intn(len(allMembers))] } - err := s.GuildMemberMove(m.GuildID, victimID, nil) + err = s.GuildMemberMove(m.GuildID, victimID, nil) if err != nil { logger.Errorf("could not kick user: %s", err) return "Failed to kick the user from the voice channel."
M src/utils.gosrc/globals/utils.go

@@ -1,29 +1,23 @@

-package src +package globals import ( "fmt" "strings" "github.com/bwmarrin/discordgo" + "github.com/kkdai/youtube/v2" ) -func getVoiceChannelID(s *discordgo.Session, m *discordgo.MessageCreate) (response string, g *discordgo.Guild, voiceChannelID string) { +func GetVoiceChannelID(s *discordgo.Session, m *discordgo.MessageCreate) (response string, g *discordgo.Guild, voiceChannelID string) { if m.Member == nil { response = "Please, use this inside a server." return } - _, err := s.Guild(m.GuildID) - if err != nil { - logger.Errorf("could not update guild: %s", err) - response = msgError - return - } - - g, err = s.State.Guild(m.GuildID) + g, err := s.State.Guild(m.GuildID) if err != nil { logger.Errorf("could not get guild: %s", err) - response = msgError + response = MsgError return }

@@ -40,12 +34,24 @@ }

return } -func formatCommand(command, guildID string) string { - return fmt.Sprintf("`%s%s`", getPrefix(guildID), command) +func (bc BotCommand) FormatHelp(command, guildID string) string { + var shortCodeStr string + if bc.ShortCode != "" { + shortCodeStr = fmt.Sprintf(" (%s)", FormatCommand(bc.ShortCode, guildID)) + } + return fmt.Sprintf(helpFmt, FormatCommand(command, guildID)+shortCodeStr, bc.Help) } -func parseUserMessage(messageContent, guildID string) (command string, args []string, ok bool) { - after, found := strings.CutPrefix(messageContent, getPrefix(guildID)) +func FormatCommand(command, guildID string) string { + return fmt.Sprintf("`%s%s`", GetPrefix(guildID), command) +} + +func FormatVideo(v *youtube.Video) string { + return fmt.Sprintf("**%s** (`%s`)", v.Title, v.Duration.String()) +} + +func ParseUserMessage(messageContent, guildID string) (command string, args []string, ok bool) { + after, found := strings.CutPrefix(messageContent, GetPrefix(guildID)) if !found { return }

@@ -55,7 +61,7 @@ command = strings.ToLower(userInput[0])

return command, userInput[1:], len(command) > 0 } -func getPrefix(guildID string) string { +func GetPrefix(guildID string) string { for _, prefix := range Config.Values.Prefixes { if prefix.Name == guildID { return prefix.Value

@@ -70,7 +76,7 @@ }

return defaultPrefix } -func setPrefix(guildID, prefixValue string) string { +func SetPrefix(guildID, prefixValue string) string { var found bool for i, prefix := range Config.Values.Prefixes { if prefix.Name == guildID {