Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f533eac6b | |||
5d327f3fd2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/dist/
|
||||
/plugins/
|
||||
/owobot
|
||||
/owobot.db
|
||||
|
12
config.go
12
config.go
@ -27,9 +27,10 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Token string `env:"TOKEN" toml:"token"`
|
||||
DBPath string `env:"DB_PATH" toml:"db_path"`
|
||||
Activity Activity `envPrefix:"ACTIVITY_" toml:"activity"`
|
||||
Token string `env:"TOKEN" toml:"token"`
|
||||
DBPath string `env:"DB_PATH" toml:"db_path"`
|
||||
PluginDir string `env:"PLUGIN_DIR" toml:"plugin_dir"`
|
||||
Activity Activity `envPrefix:"ACTIVITY_" toml:"activity"`
|
||||
}
|
||||
|
||||
type Activity struct {
|
||||
@ -40,8 +41,9 @@ type Activity struct {
|
||||
func loadConfig() (*Config, error) {
|
||||
// Create a new config struct with default values
|
||||
cfg := &Config{
|
||||
Token: "",
|
||||
DBPath: "owobot.db",
|
||||
Token: "",
|
||||
DBPath: "owobot.db",
|
||||
PluginDir: "plugins",
|
||||
Activity: Activity{
|
||||
Type: -1,
|
||||
Name: "",
|
||||
|
21
go.mod
21
go.mod
@ -3,33 +3,42 @@ module go.elara.ws/owobot
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a
|
||||
github.com/bwmarrin/discordgo v0.28.1
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/lestrrat-go/strftime v1.0.6
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/rivo/uniseg v0.4.4
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
|
||||
go.elara.ws/vercmp v0.0.0-20231003203944-671892886053
|
||||
golang.org/x/net v0.24.0
|
||||
modernc.org/sqlite v1.27.0
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gookit/color v1.5.1 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
|
83
go.sum
83
go.sum
@ -1,28 +1,52 @@
|
||||
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a h1:I1j/9FoqDN+W0ZXiSU91lJXwKCvnKBLgJKlBLYAbim4=
|
||||
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
|
||||
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
||||
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.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-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
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-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
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/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
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/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/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
||||
@ -44,6 +68,9 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd h1:wW6BtayFoKaaDeIvXRE3SZVPOscSKlYD+X3bB749+zk=
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd/go.mod h1:ib9zVtNgRKiGuoMyUqqL5aNpk+r+++YlyiVIkclVqPg=
|
||||
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=
|
||||
@ -59,25 +86,61 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae h1:d+gJUhEWSrOjrrfgeydYWEr8TTnx0DLvcVhghaOsFeE=
|
||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
|
||||
go.elara.ws/vercmp v0.0.0-20231003203944-671892886053 h1:tQ6Kyq9I0Sw9bmXQ1MZdH5EVpEc5brXe8utBCTI5pr0=
|
||||
go.elara.ws/vercmp v0.0.0-20231003203944-671892886053/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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=
|
||||
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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.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=
|
||||
golang.org/x/text v0.3.3/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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
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=
|
||||
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -34,6 +34,11 @@ var migrations embed.FS
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
// DB returns the global database instance
|
||||
func DB() *sqlx.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
// Init opens the database and applies migrations
|
||||
func Init(ctx context.Context, dsn string) error {
|
||||
g, err := sqlx.Open("sqlite", dsn)
|
||||
|
@ -21,20 +21,23 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Guild struct {
|
||||
ID string `db:"id"`
|
||||
StarboardChanID string `db:"starboard_chan_id"`
|
||||
StarboardStars int `db:"starboard_stars"`
|
||||
LogChanID string `db:"log_chan_id"`
|
||||
TicketLogChanID string `db:"ticket_log_chan_id"`
|
||||
TicketCategoryID string `db:"ticket_category_id"`
|
||||
VettingReqChanID string `db:"vetting_req_chan_id"`
|
||||
VettingRoleID string `db:"vetting_role_id"`
|
||||
TimeFormat string `db:"time_format"`
|
||||
WelcomeChanID string `db:"welcome_chan_id"`
|
||||
WelcomeMsg string `db:"welcome_msg"`
|
||||
ID string `db:"id"`
|
||||
StarboardChanID string `db:"starboard_chan_id"`
|
||||
StarboardStars int `db:"starboard_stars"`
|
||||
LogChanID string `db:"log_chan_id"`
|
||||
TicketLogChanID string `db:"ticket_log_chan_id"`
|
||||
TicketCategoryID string `db:"ticket_category_id"`
|
||||
VettingReqChanID string `db:"vetting_req_chan_id"`
|
||||
VettingRoleID string `db:"vetting_role_id"`
|
||||
TimeFormat string `db:"time_format"`
|
||||
WelcomeChanID string `db:"welcome_chan_id"`
|
||||
WelcomeMsg string `db:"welcome_msg"`
|
||||
EnabledPlugins StringSlice `db:"enabled_plugins"`
|
||||
}
|
||||
|
||||
func AllGuilds() ([]Guild, error) {
|
||||
@ -104,6 +107,39 @@ func SetWelcomeMsg(guildID, msg string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func EnablePlugin(guildID, pluginName string) error {
|
||||
var enabledPlugins StringSlice
|
||||
err := db.QueryRow("SELECT enabled_plugins FROM guilds WHERE id = ?", guildID).Scan(&enabledPlugins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if slices.Contains(enabledPlugins, pluginName) {
|
||||
return fmt.Errorf("y: ploogin %q is already enabled", pluginName)
|
||||
}
|
||||
enabledPlugins = append(enabledPlugins, pluginName)
|
||||
|
||||
_, err = db.Exec("UPDATE guilds SET enabled_plugins = ? WHERE id = ?", enabledPlugins, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
func DisablePlugin(guildID, pluginName string) error {
|
||||
var enabledPlugins StringSlice
|
||||
err := db.QueryRow("SELECT enabled_plugins FROM guilds WHERE id = ?", guildID).Scan(&enabledPlugins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i := slices.Index(enabledPlugins, pluginName); i == -1 {
|
||||
return fmt.Errorf("ploogin %q is already disabled", pluginName)
|
||||
} else {
|
||||
enabledPlugins = append(enabledPlugins[:i], enabledPlugins[i+1:]...)
|
||||
}
|
||||
|
||||
_, err = db.Exec("UPDATE guilds SET enabled_plugins = ? WHERE id = ?", enabledPlugins, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
func IsVettingMsg(msgID string) (bool, error) {
|
||||
var out bool
|
||||
err := db.QueryRow("SELECT 1 FROM guild WHERE vetting_msg_id = ?", msgID).Scan(&out)
|
||||
|
11
internal/db/migrations/2024020300.sql
Normal file
11
internal/db/migrations/2024020300.sql
Normal file
@ -0,0 +1,11 @@
|
||||
/* plugins stores information about all the plugins defined for this bot. */
|
||||
/* This will be used to let plugins perform actions when they're updated */
|
||||
CREATE TABLE plugins (
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
UNIQUE(name) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
/* Add a column to allow guilds to enable whichever plugins they want */
|
||||
ALTER TABLE guilds ADD COLUMN enabled_plugins TEXT NOT NULL DEFAULT '';
|
42
internal/db/plugins.go
Normal file
42
internal/db/plugins.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* owobot - Your server's guardian and entertainer
|
||||
* Copyright (C) 2023 owobot Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
type PluginInfo struct {
|
||||
Name string `db:"name"`
|
||||
Version string `db:"version"`
|
||||
Desc string `db:"description"`
|
||||
}
|
||||
|
||||
func (pi PluginInfo) IsValid() bool {
|
||||
if pi.Name == "" || pi.Version == "" || pi.Desc == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func AddPlugin(pi PluginInfo) error {
|
||||
_, err := db.NamedExec(`INSERT OR REPLACE INTO plugins VALUES (:name, :version, :description)`, pi)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetPlugin(name string) (out PluginInfo, err error) {
|
||||
err = db.QueryRowx("SELECT * FROM plugins WHERE name = ? LIMIT 1", name).StructScan(&out)
|
||||
return
|
||||
}
|
156
internal/db/sqltabler/sqltabler.go
Normal file
156
internal/db/sqltabler/sqltabler.go
Normal file
@ -0,0 +1,156 @@
|
||||
package sqltabler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
sqlparser "github.com/rqlite/sql"
|
||||
)
|
||||
|
||||
// Modify adds a prefix and suffix to every table name found in stmt.
|
||||
func Modify(stmt, prefix, suffix string) (string, error) {
|
||||
parser := sqlparser.NewParser(strings.NewReader(stmt))
|
||||
sb := strings.Builder{}
|
||||
for {
|
||||
s, err := parser.ParseStatement()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
modify(s, prefix, suffix)
|
||||
sb.WriteString(s.String())
|
||||
sb.WriteByte(';')
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// modify changes all the table, viee, trigger, and index names in a single statement
|
||||
func modify(stmt any, prefix, suffix string) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *sqlparser.SelectStatement:
|
||||
modifySource(stmt.Source, prefix, suffix)
|
||||
modify(stmt.WhereExpr, prefix, suffix)
|
||||
case *sqlparser.InsertStatement:
|
||||
stmt.Table.Name = prefix + stmt.Table.Name + suffix
|
||||
if stmt.Select != nil {
|
||||
modify(stmt.Select, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.UpdateStatement:
|
||||
stmt.Table.Name.Name = prefix + stmt.Table.Name.Name + suffix
|
||||
for _, assignment := range stmt.Assignments {
|
||||
modify(assignment, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.CreateTableStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
if stmt.Select != nil {
|
||||
modify(stmt.Select, prefix, suffix)
|
||||
}
|
||||
for _, col := range stmt.Columns {
|
||||
modify(col, prefix, suffix)
|
||||
}
|
||||
for _, constraint := range stmt.Constraints {
|
||||
modify(constraint, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.CreateViewStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
if stmt.Select != nil {
|
||||
modify(stmt.Select, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.AlterTableStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
if stmt.NewName != nil {
|
||||
stmt.NewName.Name = prefix + stmt.NewName.Name + suffix
|
||||
}
|
||||
if stmt.ColumnDef != nil {
|
||||
modify(stmt.ColumnDef, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.Call:
|
||||
for _, arg := range stmt.Args {
|
||||
modify(arg, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.FilterClause:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.DeleteStatement:
|
||||
stmt.Table.Name.Name = prefix + stmt.Table.Name.Name + suffix
|
||||
if stmt.WhereExpr != nil {
|
||||
modify(stmt.WhereExpr, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.AnalyzeStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
case *sqlparser.ExplainStatement:
|
||||
modify(stmt.Stmt, prefix, suffix)
|
||||
case *sqlparser.CreateIndexStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
stmt.Table.Name = prefix + stmt.Table.Name + suffix
|
||||
case *sqlparser.CreateTriggerStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
stmt.Table.Name = prefix + stmt.Table.Name + suffix
|
||||
if stmt.WhenExpr != nil {
|
||||
modify(stmt.WhenExpr, prefix, suffix)
|
||||
}
|
||||
for _, istmt := range stmt.Body {
|
||||
modify(istmt, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.CTE:
|
||||
stmt.TableName.Name = prefix + stmt.TableName.Name + suffix
|
||||
if stmt.Select != nil {
|
||||
modify(stmt.Select, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.DropTableStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
case *sqlparser.DropViewStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
case *sqlparser.DropIndexStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
case *sqlparser.DropTriggerStatement:
|
||||
stmt.Name.Name = prefix + stmt.Name.Name + suffix
|
||||
case *sqlparser.ForeignKeyConstraint:
|
||||
stmt.ForeignTable.Name = prefix + stmt.ForeignTable.Name + suffix
|
||||
case *sqlparser.OnConstraint:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.ExprList:
|
||||
for _, expr := range stmt.Exprs {
|
||||
modify(expr, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.UnaryExpr:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.BinaryExpr:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
modify(stmt.Y, prefix, suffix)
|
||||
case *sqlparser.ParenExpr:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.CastExpr:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.OrderingTerm:
|
||||
modify(stmt.X, prefix, suffix)
|
||||
case *sqlparser.Assignment:
|
||||
modify(stmt.Expr, prefix, suffix)
|
||||
case *sqlparser.ColumnDefinition:
|
||||
for _, constraint := range stmt.Constraints {
|
||||
modify(constraint, prefix, suffix)
|
||||
}
|
||||
case *sqlparser.QualifiedRef:
|
||||
if stmt.Table != nil {
|
||||
stmt.Table.Name = prefix + stmt.Table.Name + suffix
|
||||
}
|
||||
case *sqlparser.CaseExpr:
|
||||
modify(stmt.ElseExpr, prefix, suffix)
|
||||
for _, block := range stmt.Blocks {
|
||||
modify(block.Condition, prefix, suffix)
|
||||
modify(block.Body, prefix, suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func modifySource(source sqlparser.Source, prefix, suffix string) {
|
||||
switch source := source.(type) {
|
||||
case *sqlparser.QualifiedTableName:
|
||||
source.Name.Name = prefix + source.Name.Name + suffix
|
||||
case *sqlparser.JoinClause:
|
||||
modifySource(source.X, prefix, suffix)
|
||||
modifySource(source.Y, prefix, suffix)
|
||||
modify(source.Constraint, prefix, suffix)
|
||||
}
|
||||
}
|
107
internal/systems/plugins/api.go
Normal file
107
internal/systems/plugins/api.go
Normal file
@ -0,0 +1,107 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dop251/goja"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
"go.elara.ws/owobot/internal/util"
|
||||
)
|
||||
|
||||
type lockableRuntime struct {
|
||||
*sync.Mutex
|
||||
*goja.Runtime
|
||||
}
|
||||
|
||||
// Plugins is a list of plugins
|
||||
var Plugins []Plugin
|
||||
|
||||
// Plugin represents an owobot plugin
|
||||
type Plugin struct {
|
||||
Info db.PluginInfo
|
||||
Commands []Command
|
||||
VM lockableRuntime
|
||||
api *owobotAPI
|
||||
}
|
||||
|
||||
// Command represents a plugin command
|
||||
type Command struct {
|
||||
Name string
|
||||
Desc string
|
||||
Usage goja.Value
|
||||
OnExec goja.Value
|
||||
Permissions []int64
|
||||
Subcommands []Command
|
||||
}
|
||||
|
||||
func (c Command) usage() string {
|
||||
if c.Usage == nil {
|
||||
return ""
|
||||
} else {
|
||||
return c.Usage.String()
|
||||
}
|
||||
}
|
||||
|
||||
type owobotAPI struct {
|
||||
PluginInfo db.PluginInfo
|
||||
Init goja.Value
|
||||
OnEnable goja.Value
|
||||
OnDisable goja.Value
|
||||
Commands []Command
|
||||
|
||||
path string
|
||||
vm lockableRuntime
|
||||
}
|
||||
|
||||
func (oa *owobotAPI) Enabled(guildID string) bool {
|
||||
return pluginEnabled(guildID, oa.PluginInfo.Name)
|
||||
}
|
||||
|
||||
func (oa *owobotAPI) Respond(s *discordgo.Session, i *discordgo.Interaction, content string) error {
|
||||
return util.Respond(s, i, content)
|
||||
}
|
||||
|
||||
func (oa *owobotAPI) RespondEphemeral(s *discordgo.Session, i *discordgo.Interaction, content string) error {
|
||||
return util.RespondEphemeral(s, i, content)
|
||||
}
|
||||
|
||||
// On adds an event handler function for the given event type
|
||||
func (oa *owobotAPI) On(eventType string, fn goja.Value) {
|
||||
if !oa.PluginInfo.IsValid() {
|
||||
log.Warn("No plugin information provided, ignoring handler registration.").Str("path", oa.path).Send()
|
||||
return
|
||||
}
|
||||
|
||||
callable, ok := goja.AssertFunction(fn)
|
||||
if !ok {
|
||||
log.Warn("Value passed to handler registrar is not a function, ignoring.").
|
||||
Str("plugin", oa.PluginInfo.Name).
|
||||
Str("event-type", eventType).
|
||||
Send()
|
||||
return
|
||||
}
|
||||
|
||||
handlersMtx.Lock()
|
||||
defer handlersMtx.Unlock()
|
||||
|
||||
this := oa.vm.ToValue(oa)
|
||||
|
||||
handlerMap[eventType] = append(handlerMap[eventType], Handler{
|
||||
PluginName: oa.PluginInfo.Name,
|
||||
Func: func(s *discordgo.Session, data any) {
|
||||
oa.vm.Lock()
|
||||
defer oa.vm.Unlock()
|
||||
|
||||
_, err := callable(this, oa.vm.ToValue(s), oa.vm.ToValue(data))
|
||||
if err != nil {
|
||||
log.Error("Exception thrown in plugin function").
|
||||
Str("plugin", oa.PluginInfo.Name).
|
||||
Str("event-type", eventType).
|
||||
Err(err).
|
||||
Send()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
346
internal/systems/plugins/builtins/discord.go
Normal file
346
internal/systems/plugins/builtins/discord.go
Normal file
@ -0,0 +1,346 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
var Constants = map[string]any{
|
||||
"Permissions": map[string]int64{
|
||||
"ReadMessages": discordgo.PermissionViewChannel,
|
||||
"SendMessages": discordgo.PermissionSendMessages,
|
||||
"SendTTSMessages": discordgo.PermissionSendTTSMessages,
|
||||
"ManageMessages": discordgo.PermissionManageMessages,
|
||||
"EmbedLinks": discordgo.PermissionEmbedLinks,
|
||||
"AttachFiles": discordgo.PermissionAttachFiles,
|
||||
"ReadMessageHistory": discordgo.PermissionReadMessageHistory,
|
||||
"MentionEveryone": discordgo.PermissionMentionEveryone,
|
||||
"UseExternalEmojis": discordgo.PermissionUseExternalEmojis,
|
||||
"UseSlashCommands": discordgo.PermissionUseSlashCommands,
|
||||
"ManageThreads": discordgo.PermissionManageThreads,
|
||||
"CreatePublicThreads": discordgo.PermissionCreatePublicThreads,
|
||||
"CreatePrivateThreads": discordgo.PermissionCreatePrivateThreads,
|
||||
"UseExternalStickers": discordgo.PermissionUseExternalStickers,
|
||||
"SendMessagesInThreads": discordgo.PermissionSendMessagesInThreads,
|
||||
"VoicePrioritySpeaker": discordgo.PermissionVoicePrioritySpeaker,
|
||||
"VoiceStreamVideo": discordgo.PermissionVoiceStreamVideo,
|
||||
"VoiceConnect": discordgo.PermissionVoiceConnect,
|
||||
"VoiceSpeak": discordgo.PermissionVoiceSpeak,
|
||||
"VoiceMuteMembers": discordgo.PermissionVoiceMuteMembers,
|
||||
"VoiceDeafenMembers": discordgo.PermissionVoiceDeafenMembers,
|
||||
"VoiceMoveMembers": discordgo.PermissionVoiceMoveMembers,
|
||||
"VoiceUseVAD": discordgo.PermissionVoiceUseVAD,
|
||||
"VoiceRequestToSpeak": discordgo.PermissionVoiceRequestToSpeak,
|
||||
"UseActivities": discordgo.PermissionUseActivities,
|
||||
"ChangeNickname": discordgo.PermissionChangeNickname,
|
||||
"ManageNicknames": discordgo.PermissionManageNicknames,
|
||||
"ManageRoles": discordgo.PermissionManageRoles,
|
||||
"ManageWebhooks": discordgo.PermissionManageWebhooks,
|
||||
"ManageEmojis": discordgo.PermissionManageEmojis,
|
||||
"ManageEvents": discordgo.PermissionManageEvents,
|
||||
"CreateInstantInvite": discordgo.PermissionCreateInstantInvite,
|
||||
"KickMembers": discordgo.PermissionKickMembers,
|
||||
"BanMembers": discordgo.PermissionBanMembers,
|
||||
"Administrator": discordgo.PermissionAdministrator,
|
||||
"ManageChannels": discordgo.PermissionManageChannels,
|
||||
"ManageServer": discordgo.PermissionManageServer,
|
||||
"AddReactions": discordgo.PermissionAddReactions,
|
||||
"ViewAuditLogs": discordgo.PermissionViewAuditLogs,
|
||||
"ViewChannel": discordgo.PermissionViewChannel,
|
||||
"ViewGuildInsights": discordgo.PermissionViewGuildInsights,
|
||||
"ModerateMembers": discordgo.PermissionModerateMembers,
|
||||
"AllText": discordgo.PermissionAllText,
|
||||
"AllVoice": discordgo.PermissionAllVoice,
|
||||
"AllChannel": discordgo.PermissionAllChannel,
|
||||
"All": discordgo.PermissionAll,
|
||||
},
|
||||
"MessageFlag": map[string]discordgo.MessageFlags{
|
||||
"CrossPosted": discordgo.MessageFlagsCrossPosted,
|
||||
"IsCrossPosted": discordgo.MessageFlagsIsCrossPosted,
|
||||
"SuppressEmbeds": discordgo.MessageFlagsSuppressEmbeds,
|
||||
"SupressEmbeds": discordgo.MessageFlagsSupressEmbeds,
|
||||
"SourceMessageDeleted": discordgo.MessageFlagsSourceMessageDeleted,
|
||||
"Urgent": discordgo.MessageFlagsUrgent,
|
||||
"HasThread": discordgo.MessageFlagsHasThread,
|
||||
"Ephemeral": discordgo.MessageFlagsEphemeral,
|
||||
"Loading": discordgo.MessageFlagsLoading,
|
||||
"FailedToMentionSomeRolesInThread": discordgo.MessageFlagsFailedToMentionSomeRolesInThread,
|
||||
"SuppressNotifications": discordgo.MessageFlagsSuppressNotifications,
|
||||
"IsVoiceMessage": discordgo.MessageFlagsIsVoiceMessage,
|
||||
},
|
||||
"MessageType": map[string]discordgo.MessageType{
|
||||
"Default": discordgo.MessageTypeDefault,
|
||||
"RecipientAdd": discordgo.MessageTypeRecipientAdd,
|
||||
"RecipientRemove": discordgo.MessageTypeRecipientRemove,
|
||||
"Call": discordgo.MessageTypeCall,
|
||||
"ChannelNameChange": discordgo.MessageTypeChannelNameChange,
|
||||
"ChannelIconChange": discordgo.MessageTypeChannelIconChange,
|
||||
"ChannelPinnedMessage": discordgo.MessageTypeChannelPinnedMessage,
|
||||
"GuildMemberJoin": discordgo.MessageTypeGuildMemberJoin,
|
||||
"UserPremiumGuildSubscription": discordgo.MessageTypeUserPremiumGuildSubscription,
|
||||
"UserPremiumGuildSubscriptionTierOne": discordgo.MessageTypeUserPremiumGuildSubscriptionTierOne,
|
||||
"UserPremiumGuildSubscriptionTierTwo": discordgo.MessageTypeUserPremiumGuildSubscriptionTierTwo,
|
||||
"UserPremiumGuildSubscriptionTierThree": discordgo.MessageTypeUserPremiumGuildSubscriptionTierThree,
|
||||
"ChannelFollowAdd": discordgo.MessageTypeChannelFollowAdd,
|
||||
"GuildDiscoveryDisqualified": discordgo.MessageTypeGuildDiscoveryDisqualified,
|
||||
"GuildDiscoveryRequalified": discordgo.MessageTypeGuildDiscoveryRequalified,
|
||||
"ThreadCreated": discordgo.MessageTypeThreadCreated,
|
||||
"Reply": discordgo.MessageTypeReply,
|
||||
"ChatInputCommand": discordgo.MessageTypeChatInputCommand,
|
||||
"ThreadStarterMessage": discordgo.MessageTypeThreadStarterMessage,
|
||||
"ContextMenuCommand": discordgo.MessageTypeContextMenuCommand,
|
||||
},
|
||||
"Status": map[string]discordgo.Status{
|
||||
"Online": discordgo.StatusOnline,
|
||||
"Idle": discordgo.StatusIdle,
|
||||
"DoNotDisturb": discordgo.StatusDoNotDisturb,
|
||||
"Invisible": discordgo.StatusInvisible,
|
||||
"Offline": discordgo.StatusOffline,
|
||||
},
|
||||
"UserFlags": map[string]discordgo.UserFlags{
|
||||
"DiscordEmployee": discordgo.UserFlagDiscordEmployee,
|
||||
"DiscordPartner": discordgo.UserFlagDiscordPartner,
|
||||
"HypeSquadEvents": discordgo.UserFlagHypeSquadEvents,
|
||||
"BugHunterLevel1": discordgo.UserFlagBugHunterLevel1,
|
||||
"HouseBravery": discordgo.UserFlagHouseBravery,
|
||||
"HouseBrilliance": discordgo.UserFlagHouseBrilliance,
|
||||
"HouseBalance": discordgo.UserFlagHouseBalance,
|
||||
"EarlySupporter": discordgo.UserFlagEarlySupporter,
|
||||
"TeamUser": discordgo.UserFlagTeamUser,
|
||||
"System": discordgo.UserFlagSystem,
|
||||
"BugHunterLevel2": discordgo.UserFlagBugHunterLevel2,
|
||||
"VerifiedBot": discordgo.UserFlagVerifiedBot,
|
||||
"VerifiedBotDeveloper": discordgo.UserFlagVerifiedBotDeveloper,
|
||||
"DiscordCertifiedModerator": discordgo.UserFlagDiscordCertifiedModerator,
|
||||
"BotHTTPInteractions": discordgo.UserFlagBotHTTPInteractions,
|
||||
"ActiveBotDeveloper": discordgo.UserFlagActiveBotDeveloper,
|
||||
},
|
||||
"RoleFlags": map[string]discordgo.RoleFlags{
|
||||
"InPrompt": discordgo.RoleFlagInPrompt,
|
||||
},
|
||||
"SelectMenuType": map[string]discordgo.SelectMenuType{
|
||||
"String": discordgo.StringSelectMenu,
|
||||
"User": discordgo.UserSelectMenu,
|
||||
"Role": discordgo.RoleSelectMenu,
|
||||
"Mentionable": discordgo.MentionableSelectMenu,
|
||||
"Channel": discordgo.ChannelSelectMenu,
|
||||
},
|
||||
"ComponentType": map[string]discordgo.ComponentType{
|
||||
"ActionsRow": discordgo.ActionsRowComponent,
|
||||
"Button": discordgo.ButtonComponent,
|
||||
"SelectMenu": discordgo.SelectMenuComponent,
|
||||
"TextInput": discordgo.TextInputComponent,
|
||||
"UserSelectMenu": discordgo.UserSelectMenuComponent,
|
||||
"RoleSelectMenu": discordgo.RoleSelectMenuComponent,
|
||||
"MentionableSelectMenu": discordgo.MentionableSelectMenuComponent,
|
||||
"ChannelSelectMenu": discordgo.ChannelSelectMenuComponent,
|
||||
},
|
||||
"EmbedType": map[string]discordgo.EmbedType{
|
||||
"Rich": discordgo.EmbedTypeRich,
|
||||
"Image": discordgo.EmbedTypeImage,
|
||||
"Video": discordgo.EmbedTypeVideo,
|
||||
"Gifv": discordgo.EmbedTypeGifv,
|
||||
"Article": discordgo.EmbedTypeArticle,
|
||||
"Link": discordgo.EmbedTypeLink,
|
||||
},
|
||||
"MfaLevel": map[string]discordgo.MfaLevel{
|
||||
"None": discordgo.MfaLevelNone,
|
||||
"Elevated": discordgo.MfaLevelElevated,
|
||||
},
|
||||
"PermissionOverwriteType": map[string]discordgo.PermissionOverwriteType{
|
||||
"Role": discordgo.PermissionOverwriteTypeRole,
|
||||
"Member": discordgo.PermissionOverwriteTypeMember,
|
||||
},
|
||||
"PremiumTier": map[string]discordgo.PremiumTier{
|
||||
"None": discordgo.PremiumTierNone,
|
||||
"Tier1": discordgo.PremiumTier1,
|
||||
"Tier2": discordgo.PremiumTier2,
|
||||
"Tier3": discordgo.PremiumTier3,
|
||||
},
|
||||
"SelectMenuDefaultValueType": map[string]discordgo.SelectMenuDefaultValueType{
|
||||
"User": discordgo.SelectMenuDefaultValueUser,
|
||||
"Role": discordgo.SelectMenuDefaultValueRole,
|
||||
"Channel": discordgo.SelectMenuDefaultValueChannel,
|
||||
},
|
||||
"StageInstancePrivacyLevel": map[string]discordgo.StageInstancePrivacyLevel{
|
||||
"Public": discordgo.StageInstancePrivacyLevelPublic,
|
||||
"GuildOnly": discordgo.StageInstancePrivacyLevelGuildOnly,
|
||||
},
|
||||
"StickerFormat": map[string]discordgo.StickerFormat{
|
||||
"PNG": discordgo.StickerFormatTypePNG,
|
||||
"APNG": discordgo.StickerFormatTypeAPNG,
|
||||
"Lottie": discordgo.StickerFormatTypeLottie,
|
||||
"GIF": discordgo.StickerFormatTypeGIF,
|
||||
},
|
||||
"StickerType": map[string]discordgo.StickerType{
|
||||
"Standard": discordgo.StickerTypeStandard,
|
||||
"Guild": discordgo.StickerTypeGuild,
|
||||
},
|
||||
"ExpireBehavior": map[string]discordgo.ExpireBehavior{
|
||||
"RemoveRole": discordgo.ExpireBehaviorRemoveRole,
|
||||
"Kick": discordgo.ExpireBehaviorKick,
|
||||
},
|
||||
"ExplicitContentFilterLevel": map[string]discordgo.ExplicitContentFilterLevel{
|
||||
"Disabled": discordgo.ExplicitContentFilterDisabled,
|
||||
"MembersWithoutRoles": discordgo.ExplicitContentFilterMembersWithoutRoles,
|
||||
"AllMembers": discordgo.ExplicitContentFilterAllMembers,
|
||||
},
|
||||
"ForumLayout": map[string]discordgo.ForumLayout{
|
||||
"NotSet": discordgo.ForumLayoutNotSet,
|
||||
"ListView": discordgo.ForumLayoutListView,
|
||||
"GalleryView": discordgo.ForumLayoutGalleryView,
|
||||
},
|
||||
"ForumSortOrderType": map[string]discordgo.ForumSortOrderType{
|
||||
"LatestActivity": discordgo.ForumSortOrderLatestActivity,
|
||||
"CreationDate": discordgo.ForumSortOrderCreationDate,
|
||||
},
|
||||
"GuildFeature": map[string]discordgo.GuildFeature{
|
||||
"AnimatedBanner": discordgo.GuildFeatureAnimatedBanner,
|
||||
"AnimatedIcon": discordgo.GuildFeatureAnimatedIcon,
|
||||
"AutoModeration": discordgo.GuildFeatureAutoModeration,
|
||||
"Banner": discordgo.GuildFeatureBanner,
|
||||
"Community": discordgo.GuildFeatureCommunity,
|
||||
"Discoverable": discordgo.GuildFeatureDiscoverable,
|
||||
"Featurable": discordgo.GuildFeatureFeaturable,
|
||||
"InviteSplash": discordgo.GuildFeatureInviteSplash,
|
||||
"MemberVerificationGateEnabled": discordgo.GuildFeatureMemberVerificationGateEnabled,
|
||||
"MonetizationEnabled": discordgo.GuildFeatureMonetizationEnabled,
|
||||
"MoreStickers": discordgo.GuildFeatureMoreStickers,
|
||||
"News": discordgo.GuildFeatureNews,
|
||||
"Partnered": discordgo.GuildFeaturePartnered,
|
||||
"PreviewEnabled": discordgo.GuildFeaturePreviewEnabled,
|
||||
"PrivateThreads": discordgo.GuildFeaturePrivateThreads,
|
||||
"RoleIcons": discordgo.GuildFeatureRoleIcons,
|
||||
"TicketedEventsEnabled": discordgo.GuildFeatureTicketedEventsEnabled,
|
||||
"VanityURL": discordgo.GuildFeatureVanityURL,
|
||||
"Verified": discordgo.GuildFeatureVerified,
|
||||
"VipRegions": discordgo.GuildFeatureVipRegions,
|
||||
"WelcomeScreenEnabled": discordgo.GuildFeatureWelcomeScreenEnabled,
|
||||
},
|
||||
"GuildNSFWLevel": map[string]discordgo.GuildNSFWLevel{
|
||||
"Default": discordgo.GuildNSFWLevelDefault,
|
||||
"Explicit": discordgo.GuildNSFWLevelExplicit,
|
||||
"Safe": discordgo.GuildNSFWLevelSafe,
|
||||
"AgeRestricted": discordgo.GuildNSFWLevelAgeRestricted,
|
||||
},
|
||||
"GuildOnboardingMode": map[string]discordgo.GuildOnboardingMode{
|
||||
"Default": discordgo.GuildOnboardingModeDefault,
|
||||
"Advanced": discordgo.GuildOnboardingModeAdvanced,
|
||||
},
|
||||
"GuildOnboardingPromptType": map[string]discordgo.GuildOnboardingPromptType{
|
||||
"MultipleChoice": discordgo.GuildOnboardingPromptTypeMultipleChoice,
|
||||
"Dropdown": discordgo.GuildOnboardingPromptTypeDropdown,
|
||||
},
|
||||
"GuildScheduledEventEntityType": map[string]discordgo.GuildScheduledEventEntityType{
|
||||
"StageInstance": discordgo.GuildScheduledEventEntityTypeStageInstance,
|
||||
"Voice": discordgo.GuildScheduledEventEntityTypeVoice,
|
||||
"External": discordgo.GuildScheduledEventEntityTypeExternal,
|
||||
},
|
||||
"GuildScheduledEventPrivacyLevel": map[string]discordgo.GuildScheduledEventPrivacyLevel{
|
||||
"GuildOnly": discordgo.GuildScheduledEventPrivacyLevelGuildOnly,
|
||||
},
|
||||
"GuildScheduledEventStatus": map[string]discordgo.GuildScheduledEventStatus{
|
||||
"Scheduled": discordgo.GuildScheduledEventStatusScheduled,
|
||||
"Active": discordgo.GuildScheduledEventStatusActive,
|
||||
"Completed": discordgo.GuildScheduledEventStatusCompleted,
|
||||
"Canceled": discordgo.GuildScheduledEventStatusCanceled,
|
||||
},
|
||||
"Intent": map[string]discordgo.Intent{
|
||||
"Guilds": discordgo.IntentGuilds,
|
||||
"GuildMembers": discordgo.IntentGuildMembers,
|
||||
"GuildModeration": discordgo.IntentGuildModeration,
|
||||
"GuildEmojis": discordgo.IntentGuildEmojis,
|
||||
"GuildIntegrations": discordgo.IntentGuildIntegrations,
|
||||
"GuildWebhooks": discordgo.IntentGuildWebhooks,
|
||||
"GuildInvites": discordgo.IntentGuildInvites,
|
||||
"GuildVoiceStates": discordgo.IntentGuildVoiceStates,
|
||||
"GuildPresences": discordgo.IntentGuildPresences,
|
||||
"GuildMessages": discordgo.IntentGuildMessages,
|
||||
"GuildMessageReactions": discordgo.IntentGuildMessageReactions,
|
||||
"GuildMessageTyping": discordgo.IntentGuildMessageTyping,
|
||||
"GuildBans": discordgo.IntentGuildBans,
|
||||
"DirectMessages": discordgo.IntentDirectMessages,
|
||||
"DirectMessageReactions": discordgo.IntentDirectMessageReactions,
|
||||
"DirectMessageTyping": discordgo.IntentDirectMessageTyping,
|
||||
"MessageContent": discordgo.IntentMessageContent,
|
||||
"GuildScheduledEvents": discordgo.IntentGuildScheduledEvents,
|
||||
"AutoModerationConfiguration": discordgo.IntentAutoModerationConfiguration,
|
||||
"AutoModerationExecution": discordgo.IntentAutoModerationExecution,
|
||||
"AllWithoutPrivileged": discordgo.IntentsAllWithoutPrivileged,
|
||||
"IntentsAll": discordgo.IntentsAll,
|
||||
"IntentsNone": discordgo.IntentsNone,
|
||||
},
|
||||
"InteractionResponseType": map[string]discordgo.InteractionResponseType{
|
||||
"Pong": discordgo.InteractionResponsePong,
|
||||
"ChannelMessageWithSource": discordgo.InteractionResponseChannelMessageWithSource,
|
||||
"DeferredChannelMessageWithSource": discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
||||
"DeferredMessageUpdate": discordgo.InteractionResponseDeferredMessageUpdate,
|
||||
"UpdateMessage": discordgo.InteractionResponseUpdateMessage,
|
||||
"ApplicationCommandAutocompleteResult": discordgo.InteractionApplicationCommandAutocompleteResult,
|
||||
"Modal": discordgo.InteractionResponseModal,
|
||||
},
|
||||
"InteractionType": map[string]discordgo.InteractionType{
|
||||
"Ping": discordgo.InteractionPing,
|
||||
"ApplicationCommand": discordgo.InteractionApplicationCommand,
|
||||
"MessageComponent": discordgo.InteractionMessageComponent,
|
||||
"ApplicationCommandAutocomplete": discordgo.InteractionApplicationCommandAutocomplete,
|
||||
"ModalSubmit": discordgo.InteractionModalSubmit,
|
||||
},
|
||||
"InviteTargetType": map[string]discordgo.InviteTargetType{
|
||||
"Stream": discordgo.InviteTargetStream,
|
||||
"EmbeddedApplication": discordgo.InviteTargetEmbeddedApplication,
|
||||
},
|
||||
"Locale": map[string]discordgo.Locale{
|
||||
"EnglishUS": discordgo.EnglishUS,
|
||||
"EnglishGB": discordgo.EnglishGB,
|
||||
"Bulgarian": discordgo.Bulgarian,
|
||||
"ChineseCN": discordgo.ChineseCN,
|
||||
"ChineseTW": discordgo.ChineseTW,
|
||||
"Croatian": discordgo.Croatian,
|
||||
"Czech": discordgo.Czech,
|
||||
"Danish": discordgo.Danish,
|
||||
"Dutch": discordgo.Dutch,
|
||||
"Finnish": discordgo.Finnish,
|
||||
"French": discordgo.French,
|
||||
"German": discordgo.German,
|
||||
"Greek": discordgo.Greek,
|
||||
"Hindi": discordgo.Hindi,
|
||||
"Hungarian": discordgo.Hungarian,
|
||||
"Italian": discordgo.Italian,
|
||||
"Japanese": discordgo.Japanese,
|
||||
"Korean": discordgo.Korean,
|
||||
"Lithuanian": discordgo.Lithuanian,
|
||||
"Norwegian": discordgo.Norwegian,
|
||||
"Polish": discordgo.Polish,
|
||||
"PortugueseBR": discordgo.PortugueseBR,
|
||||
"Romanian": discordgo.Romanian,
|
||||
"Russian": discordgo.Russian,
|
||||
"SpanishES": discordgo.SpanishES,
|
||||
"SpanishLATAM": discordgo.SpanishLATAM,
|
||||
"Swedish": discordgo.Swedish,
|
||||
"Thai": discordgo.Thai,
|
||||
"Turkish": discordgo.Turkish,
|
||||
"Ukrainian": discordgo.Ukrainian,
|
||||
"Vietnamese": discordgo.Vietnamese,
|
||||
"Unknown": discordgo.Unknown,
|
||||
},
|
||||
"MemberFlags": map[string]discordgo.MemberFlags{
|
||||
"DidRejoin": discordgo.MemberFlagDidRejoin,
|
||||
"CompletedOnboarding": discordgo.MemberFlagCompletedOnboarding,
|
||||
"BypassesVerification": discordgo.MemberFlagBypassesVerification,
|
||||
"StartedOnboarding": discordgo.MemberFlagStartedOnboarding,
|
||||
},
|
||||
"MembershipState": map[string]discordgo.MembershipState{
|
||||
"Invited": discordgo.MembershipStateInvited,
|
||||
"Accepted": discordgo.MembershipStateAccepted,
|
||||
},
|
||||
"MessageActivityType": map[string]discordgo.MessageActivityType{
|
||||
"Join": discordgo.MessageActivityTypeJoin,
|
||||
"Spectate": discordgo.MessageActivityTypeSpectate,
|
||||
"Listen": discordgo.MessageActivityTypeListen,
|
||||
"JoinRequest": discordgo.MessageActivityTypeJoinRequest,
|
||||
},
|
||||
"MessageNotifications": map[string]discordgo.MessageNotifications{
|
||||
"AllMessages": discordgo.MessageNotificationsAllMessages,
|
||||
"OnlyMentions": discordgo.MessageNotificationsOnlyMentions,
|
||||
},
|
||||
}
|
118
internal/systems/plugins/builtins/fetch.go
Normal file
118
internal/systems/plugins/builtins/fetch.go
Normal file
@ -0,0 +1,118 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Options contains options for the JavaScript fetch function
|
||||
type Options struct {
|
||||
Method string
|
||||
Body string
|
||||
Headers map[string]any
|
||||
HandleCookies *bool
|
||||
}
|
||||
|
||||
// Response contains the response object for the JavaScript fetch function
|
||||
type Response struct {
|
||||
Status string
|
||||
StatusCode int
|
||||
Headers http.Header
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (r Response) JSON() (v any, err error) {
|
||||
err = json.Unmarshal(r.body, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (r Response) String() string {
|
||||
return string(r.body)
|
||||
}
|
||||
|
||||
// FetchFunc is the fetch function signature
|
||||
type FetchFunc = func(string, *Options) (*Response, error)
|
||||
|
||||
func fetch(pluginName, pluginVersion string) FetchFunc {
|
||||
// cookiejar.New always returns a nil error
|
||||
jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
|
||||
return func(url string, opts *Options) (*Response, error) {
|
||||
if opts == nil {
|
||||
t := true
|
||||
opts = &Options{HandleCookies: &t}
|
||||
}
|
||||
|
||||
if opts.HandleCookies == nil {
|
||||
t := true
|
||||
opts.HandleCookies = &t
|
||||
}
|
||||
|
||||
if opts.Method == "" {
|
||||
opts.Method = http.MethodGet
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(opts.Method, url, strings.NewReader(opts.Body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range opts.Headers {
|
||||
req.Header.Add(key, value.(string))
|
||||
}
|
||||
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", getUserAgent(pluginName, pluginVersion))
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
if *opts.HandleCookies {
|
||||
client.Jar = jar
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Status: resp.Status,
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: resp.Header,
|
||||
body: responseBody,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getUserAgent uses the built in vcs information to generate a user agent string
|
||||
func getUserAgent(pluginName, pluginVersion string) string {
|
||||
commit := "unknown"
|
||||
modified := "unmodified"
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
switch setting.Key {
|
||||
case "vcs.revision":
|
||||
commit = setting.Value[:8]
|
||||
case "vcs.modified":
|
||||
if setting.Value == "true" {
|
||||
modified = "modified"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("owobot/%s (%s; %s/%s)", commit, modified, pluginName, pluginVersion)
|
||||
}
|
42
internal/systems/plugins/builtins/internal.go
Normal file
42
internal/systems/plugins/builtins/internal.go
Normal file
@ -0,0 +1,42 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"go.elara.ws/owobot/internal/cache"
|
||||
"go.elara.ws/owobot/internal/systems/eventlog"
|
||||
"go.elara.ws/owobot/internal/systems/tickets"
|
||||
)
|
||||
|
||||
type eventLogAPI struct{}
|
||||
|
||||
func (eventLogAPI) Log(s *discordgo.Session, guildID string, e eventlog.Entry) error {
|
||||
return eventlog.Log(s, guildID, e)
|
||||
}
|
||||
|
||||
type ticketsAPI struct{}
|
||||
|
||||
func (ticketsAPI) Open(s *discordgo.Session, guildID string, user, executor *discordgo.User) (string, error) {
|
||||
return tickets.Open(s, guildID, user, executor)
|
||||
}
|
||||
|
||||
func (ticketsAPI) Close(s *discordgo.Session, guildID string, user, executor *discordgo.User) {
|
||||
tickets.Close(s, guildID, user, executor)
|
||||
}
|
||||
|
||||
type cacheAPI struct{}
|
||||
|
||||
func (cacheAPI) Channel(s *discordgo.Session, guildID, channelID string) (*discordgo.Channel, error) {
|
||||
return cache.Channel(s, guildID, channelID)
|
||||
}
|
||||
|
||||
func (cacheAPI) Member(s *discordgo.Session, guildID, userID string) (*discordgo.Member, error) {
|
||||
return cache.Member(s, guildID, userID)
|
||||
}
|
||||
|
||||
func (cacheAPI) Role(s *discordgo.Session, guildID, roleID string) (*discordgo.Role, error) {
|
||||
return cache.Role(s, guildID, roleID)
|
||||
}
|
||||
|
||||
func (cacheAPI) Roles(s *discordgo.Session, guildID string) ([]*discordgo.Role, error) {
|
||||
return cache.Roles(s, guildID)
|
||||
}
|
19
internal/systems/plugins/builtins/register.go
Normal file
19
internal/systems/plugins/builtins/register.go
Normal file
@ -0,0 +1,19 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
// Register registers all the owobot APIs in JavaScript.
|
||||
func Register(vm *goja.Runtime, pluginName, pluginVersion string) error {
|
||||
return errors.Join(
|
||||
vm.GlobalObject().Set("sql", sqlAPI{pluginName: pluginName}),
|
||||
vm.GlobalObject().Set("vercmp", vercmpAPI{}),
|
||||
vm.GlobalObject().Set("cache", cacheAPI{}),
|
||||
vm.GlobalObject().Set("tickets", ticketsAPI{}),
|
||||
vm.GlobalObject().Set("eventlog", eventLogAPI{}),
|
||||
vm.GlobalObject().Set("fetch", fetch(pluginName, pluginVersion)),
|
||||
)
|
||||
}
|
59
internal/systems/plugins/builtins/sql.go
Normal file
59
internal/systems/plugins/builtins/sql.go
Normal file
@ -0,0 +1,59 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
"go.elara.ws/owobot/internal/db/sqltabler"
|
||||
)
|
||||
|
||||
type sqlAPI struct {
|
||||
pluginName string
|
||||
}
|
||||
|
||||
func (s sqlAPI) Exec(query string, args ...any) error {
|
||||
newQuery, err := sqltabler.Modify(query, "_owobot_plugin_", "_"+s.pluginName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.DB().Exec(newQuery, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s sqlAPI) Query(query string, args ...any) ([]map[string]any, error) {
|
||||
newQuery, err := sqltabler.Modify(query, "_owobot_plugin_", "_"+s.pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := db.DB().Queryx(newQuery, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rowsToMap(rows)
|
||||
}
|
||||
|
||||
func (s sqlAPI) QueryOne(query string, args ...any) (map[string]any, error) {
|
||||
newQuery, err := sqltabler.Modify(query, "_owobot_plugin_", "_"+s.pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row := db.DB().QueryRowx(newQuery, args...)
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string]any{}
|
||||
return out, row.MapScan(out)
|
||||
}
|
||||
|
||||
func rowsToMap(rows *sqlx.Rows) ([]map[string]any, error) {
|
||||
var out []map[string]any
|
||||
for rows.Next() {
|
||||
resultMap := map[string]any{}
|
||||
err := rows.MapScan(resultMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, resultMap)
|
||||
}
|
||||
|
||||
return out, rows.Err()
|
||||
}
|
21
internal/systems/plugins/builtins/vercmp.go
Normal file
21
internal/systems/plugins/builtins/vercmp.go
Normal file
@ -0,0 +1,21 @@
|
||||
package builtins
|
||||
|
||||
import "go.elara.ws/vercmp"
|
||||
|
||||
type vercmpAPI struct{}
|
||||
|
||||
func (vercmpAPI) Newer(v1, v2 string) bool {
|
||||
return vercmp.Compare(v1, v2) == 1
|
||||
}
|
||||
|
||||
func (vercmpAPI) Older(v1, v2 string) bool {
|
||||
return vercmp.Compare(v1, v2) == -1
|
||||
}
|
||||
|
||||
func (vercmpAPI) Equal(v1, v2 string) bool {
|
||||
return vercmp.Compare(v1, v2) == 0
|
||||
}
|
||||
|
||||
func (vercmpAPI) Compare(v1, v2 string) int {
|
||||
return vercmp.Compare(v1, v2)
|
||||
}
|
265
internal/systems/plugins/commands.go
Normal file
265
internal/systems/plugins/commands.go
Normal file
@ -0,0 +1,265 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/kballard/go-shellquote"
|
||||
"go.elara.ws/owobot/internal/util"
|
||||
)
|
||||
|
||||
// pluginadmCmd handles the `/plugin` command and routes it to the correct subcommand.
|
||||
func pluginadmCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
switch name := data.Options[0].Name; name {
|
||||
case "list":
|
||||
return listCmd(s, i)
|
||||
case "enable":
|
||||
return enableCmd(s, i)
|
||||
case "disable":
|
||||
return disableCmd(s, i)
|
||||
default:
|
||||
return fmt.Errorf("unknown pluginadm subcommand: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// listCmd handles the `/plugin list` command.
|
||||
func listCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
sb := strings.Builder{}
|
||||
for _, plugin := range Plugins {
|
||||
sb.WriteString(plugin.Info.Name)
|
||||
sb.WriteString(" (")
|
||||
sb.WriteString(plugin.Info.Version)
|
||||
sb.WriteString(`): "`)
|
||||
sb.WriteString(plugin.Info.Desc)
|
||||
sb.WriteByte('"')
|
||||
if pluginEnabled(i.GuildID, plugin.Info.Name) {
|
||||
sb.WriteString(" *")
|
||||
}
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
return util.RespondEphemeral(s, i.Interaction, sb.String())
|
||||
}
|
||||
|
||||
// enableCmd handles the `/plugin enable` command.
|
||||
func enableCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
pluginName := data.Options[0].Options[0].StringValue()
|
||||
|
||||
plugin, ok := findPlugin(pluginName)
|
||||
if !ok {
|
||||
return fmt.Errorf("no such plugin: %q", pluginName)
|
||||
}
|
||||
|
||||
err := enablePlugin(i.GuildID, pluginName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if plugin.api.OnEnable != nil {
|
||||
callable, ok := goja.AssertFunction(plugin.api.OnEnable)
|
||||
if !ok {
|
||||
return fmt.Errorf("onEnable value is not callable")
|
||||
}
|
||||
|
||||
_, err := callable(plugin.VM.ToValue(plugin.api), plugin.VM.ToValue(i.GuildID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s onEnable: %w", plugin.Info.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return util.RespondEphemeral(s, i.Interaction, fmt.Sprintf("Successfully enabled the %q plugin!", pluginName))
|
||||
}
|
||||
|
||||
// disableCmd handles the `/plugin disable` command.
|
||||
func disableCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
pluginName := data.Options[0].Options[0].StringValue()
|
||||
|
||||
plugin, ok := findPlugin(pluginName)
|
||||
if !ok {
|
||||
return fmt.Errorf("no such plugin: %q", pluginName)
|
||||
}
|
||||
|
||||
err := disablePlugin(i.GuildID, pluginName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if plugin.api.OnDisable != nil {
|
||||
callable, ok := goja.AssertFunction(plugin.api.OnDisable)
|
||||
if !ok {
|
||||
return fmt.Errorf("onDisable value is not callable")
|
||||
}
|
||||
|
||||
plugin.VM.Lock()
|
||||
defer plugin.VM.Unlock()
|
||||
|
||||
_, err := callable(plugin.VM.ToValue(plugin.api), plugin.VM.ToValue(i.GuildID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s onDisable: %w", plugin.Info.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return util.RespondEphemeral(s, i.Interaction, fmt.Sprintf("Successfully disabled the %q plugin", pluginName))
|
||||
}
|
||||
|
||||
func pluginCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
switch name := data.Options[0].Name; name {
|
||||
case "run":
|
||||
return pluginRunCmd(s, i)
|
||||
case "help":
|
||||
return pluginHelpCmd(s, i)
|
||||
default:
|
||||
return fmt.Errorf("unknown plugin subcommand: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// pluginHelpCmd handles the `/phelp` command.
|
||||
func pluginHelpCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
args, err := shellquote.Split(cmdStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, plugin := range Plugins {
|
||||
if !pluginEnabled(i.GuildID, plugin.Info.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, _, ok := findCmd(plugin.Commands, args)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, perm := range cmd.Permissions {
|
||||
if i.Member.Permissions&perm == 0 {
|
||||
return errors.New("you don't have permission to execute this command")
|
||||
}
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("Usage: `")
|
||||
sb.WriteString(cmdStr)
|
||||
if usage := cmd.usage(); usage != "" {
|
||||
sb.WriteString(" " + usage)
|
||||
}
|
||||
sb.WriteByte('`')
|
||||
|
||||
sb.WriteString("\n\n")
|
||||
sb.WriteString("Description:\n```text\n")
|
||||
sb.WriteString(cmd.Desc)
|
||||
sb.WriteString("\n```\n")
|
||||
|
||||
if len(cmd.Subcommands) > 0 {
|
||||
sb.WriteString("Subcommands:\n")
|
||||
for _, subcmd := range cmd.Subcommands {
|
||||
sb.WriteString("- `")
|
||||
sb.WriteString(subcmd.Name)
|
||||
if usage := subcmd.usage(); usage != "" {
|
||||
sb.WriteString(" " + usage)
|
||||
}
|
||||
sb.WriteString("`: `")
|
||||
sb.WriteString(subcmd.Desc)
|
||||
sb.WriteString("`\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
Embeds: []*discordgo.MessageEmbed{{
|
||||
Title: "Command `" + cmd.Name + "`",
|
||||
Description: sb.String(),
|
||||
}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return fmt.Errorf("command not found: %q", args[0])
|
||||
}
|
||||
|
||||
// pluginRunCmd handles the `/pluginRunCmd` command.
|
||||
func pluginRunCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
args, err := shellquote.Split(cmdStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, plugin := range Plugins {
|
||||
if !pluginEnabled(i.GuildID, plugin.Info.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, newArgs, ok := findCmd(plugin.Commands, args)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, perm := range cmd.Permissions {
|
||||
if i.Member.Permissions&perm == 0 {
|
||||
return errors.New("you don't have permission to execute this command")
|
||||
}
|
||||
}
|
||||
|
||||
callable, ok := goja.AssertFunction(cmd.OnExec)
|
||||
if !ok {
|
||||
return fmt.Errorf("value in onExec is not callable")
|
||||
}
|
||||
|
||||
plugin.VM.Lock()
|
||||
_, err = callable(
|
||||
plugin.VM.ToValue(cmd),
|
||||
plugin.VM.ToValue(s),
|
||||
plugin.VM.ToValue(i),
|
||||
plugin.VM.ToValue(newArgs),
|
||||
)
|
||||
plugin.VM.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("command not found: %q", args[0])
|
||||
}
|
||||
|
||||
func findPlugin(name string) (Plugin, bool) {
|
||||
for _, plugin := range Plugins {
|
||||
if plugin.Info.Name == name {
|
||||
return plugin, true
|
||||
}
|
||||
}
|
||||
return Plugin{}, false
|
||||
}
|
||||
|
||||
func findCmd(cmds []Command, args []string) (Command, []string, bool) {
|
||||
if len(args) == 0 {
|
||||
return Command{}, nil, false
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
if args[0] != cmd.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(cmd.Subcommands) != 0 && len(args) > 1 {
|
||||
subcmd, newArgs, ok := findCmd(cmd.Subcommands, args[1:])
|
||||
if ok {
|
||||
return subcmd, newArgs, true
|
||||
}
|
||||
}
|
||||
|
||||
return cmd, args[1:], true
|
||||
}
|
||||
return Command{}, nil, false
|
||||
}
|
45
internal/systems/plugins/enable.go
Normal file
45
internal/systems/plugins/enable.go
Normal file
@ -0,0 +1,45 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
)
|
||||
|
||||
var enabled = map[string][]string{}
|
||||
|
||||
func loadEnabled() error {
|
||||
guilds, err := db.AllGuilds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, guild := range guilds {
|
||||
enabled[guild.ID] = []string(guild.EnabledPlugins)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enablePlugin(guildID, pluginName string) error {
|
||||
if slices.Contains(enabled[guildID], pluginName) {
|
||||
return fmt.Errorf("plugin %q is already enabled", pluginName)
|
||||
}
|
||||
enabled[guildID] = append(enabled[guildID], pluginName)
|
||||
return db.EnablePlugin(guildID, pluginName)
|
||||
}
|
||||
|
||||
func disablePlugin(guildID, pluginName string) error {
|
||||
if i := slices.Index(enabled[guildID], pluginName); i > -1 {
|
||||
enabled[guildID] = append(enabled[guildID][:i], enabled[guildID][i+1:]...)
|
||||
} else {
|
||||
return fmt.Errorf("plugin %q is already disabled", pluginName)
|
||||
}
|
||||
return db.DisablePlugin(guildID, pluginName)
|
||||
}
|
||||
|
||||
func pluginEnabled(guildID, pluginName string) bool {
|
||||
if guildID == "" {
|
||||
return false
|
||||
}
|
||||
return slices.Contains(enabled[guildID], pluginName)
|
||||
}
|
34
internal/systems/plugins/field_mapper.go
Normal file
34
internal/systems/plugins/field_mapper.go
Normal file
@ -0,0 +1,34 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type lowerCamelNameMapper struct{}
|
||||
|
||||
func (lowerCamelNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
|
||||
return toLowerCamel(f.Name)
|
||||
}
|
||||
|
||||
func (lowerCamelNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
|
||||
return toLowerCamel(m.Name)
|
||||
}
|
||||
|
||||
func toLowerCamel(name string) string {
|
||||
if isUpper(name) {
|
||||
return strings.ToLower(name)
|
||||
} else {
|
||||
return strings.ToLower(name[:1]) + name[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func isUpper(s string) bool {
|
||||
for _, char := range s {
|
||||
if unicode.IsLower(char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
134
internal/systems/plugins/handlers.go
Normal file
134
internal/systems/plugins/handlers.go
Normal file
@ -0,0 +1,134 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// HandlerFunc is an event handler function.
|
||||
type HandlerFunc func(session *discordgo.Session, data any)
|
||||
|
||||
// Handler represents a plugin event handler.
|
||||
type Handler struct {
|
||||
PluginName string
|
||||
Func HandlerFunc
|
||||
}
|
||||
|
||||
var (
|
||||
handlersMtx = sync.Mutex{}
|
||||
handlerMap = map[string][]Handler{}
|
||||
)
|
||||
|
||||
// handlePluginEvent handles any discord event we receive and
|
||||
// routes it to the appropriate plugin handler(s).
|
||||
func handlePluginEvent(s *discordgo.Session, data any) {
|
||||
name := reflect.TypeOf(data).Elem().Name()
|
||||
handlers, ok := handlerMap[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, h := range handlers {
|
||||
if !pluginEnabled(eventGuildID(data), h.PluginName) {
|
||||
continue
|
||||
}
|
||||
|
||||
h.Func(s, data)
|
||||
}
|
||||
}
|
||||
|
||||
// eventGuildID uses reflection to get the guild ID from an event
|
||||
func eventGuildID(event any) string {
|
||||
evt := reflect.ValueOf(event)
|
||||
|
||||
for evt.Kind() == reflect.Pointer {
|
||||
evt = evt.Elem()
|
||||
}
|
||||
|
||||
if evt.Kind() != reflect.Struct {
|
||||
return ""
|
||||
}
|
||||
|
||||
if id := evt.FieldByName("GuildID"); id.IsValid() {
|
||||
return id.String()
|
||||
} else if guild := evt.FieldByName("Guild"); guild.IsValid() {
|
||||
if id := guild.FieldByName("ID"); id.IsValid() {
|
||||
return id.String()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// handleAutocomplete handles autocomplete events for the /plugin run command.
|
||||
func handleAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
if i.Type != discordgo.InteractionApplicationCommandAutocomplete {
|
||||
return
|
||||
}
|
||||
|
||||
data := i.ApplicationCommandData()
|
||||
if data.Name != "plugin" {
|
||||
return
|
||||
}
|
||||
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Choices: getAllChoices(i.GuildID, cmdStr, i.Member),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// getAllChoices gets possible command strings for each plugin and converts them
|
||||
// to Discord command options.
|
||||
func getAllChoices(guildID, partial string, member *discordgo.Member) (out []*discordgo.ApplicationCommandOptionChoice) {
|
||||
for _, plugin := range Plugins {
|
||||
if !pluginEnabled(guildID, plugin.Info.Name) {
|
||||
continue
|
||||
}
|
||||
out = append(out, getChoiceStrs(partial, "", plugin.Commands, member)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// getChoiceStrs recursively looks through every command in cmds,
|
||||
// and generates a list of strings to use as autocomplete options.
|
||||
func getChoiceStrs(partial, prefix string, cmds []Command, member *discordgo.Member) []*discordgo.ApplicationCommandOptionChoice {
|
||||
if len(cmds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
partial = strings.TrimSpace(partial)
|
||||
var out []*discordgo.ApplicationCommandOptionChoice
|
||||
|
||||
for _, cmd := range cmds {
|
||||
for _, perm := range cmd.Permissions {
|
||||
if member.Permissions&perm == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sub := getChoiceStrs(strings.TrimPrefix(partial, cmd.Name), cmd.Name+" ", cmd.Subcommands, member)
|
||||
out = append(out, sub...)
|
||||
|
||||
if cmd.OnExec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
qualifiedCmd := prefix + cmd.Name
|
||||
|
||||
if strings.Contains(qualifiedCmd, partial) {
|
||||
out = append(out, &discordgo.ApplicationCommandOptionChoice{
|
||||
Name: qualifiedCmd,
|
||||
Value: qualifiedCmd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
178
internal/systems/plugins/init.go
Normal file
178
internal/systems/plugins/init.go
Normal file
@ -0,0 +1,178 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dop251/goja"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
"go.elara.ws/owobot/internal/systems/commands"
|
||||
"go.elara.ws/owobot/internal/systems/plugins/builtins"
|
||||
"go.elara.ws/owobot/internal/util"
|
||||
)
|
||||
|
||||
func Init(s *discordgo.Session) error {
|
||||
if err := loadEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commands.Register(s, pluginCmd, &discordgo.ApplicationCommand{
|
||||
Name: "plugin",
|
||||
Description: "Interact with the plugins on this server",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "run",
|
||||
Description: "Run a plugin command",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "cmd",
|
||||
Description: "The plugin command to run",
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "help",
|
||||
Description: "See how to use a plugin command",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "cmd",
|
||||
Description: "The plugin command to help with",
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
commands.Register(s, pluginadmCmd, &discordgo.ApplicationCommand{
|
||||
Name: "pluginadm",
|
||||
Description: "Manage dynamic plugins for your server",
|
||||
DefaultMemberPermissions: util.Pointer[int64](discordgo.PermissionManageServer),
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "list",
|
||||
Description: "List all available plugins",
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "enable",
|
||||
Description: "Enable a plugin in this guild",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "plugin",
|
||||
Description: "The name of the plugin to enable",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "disable",
|
||||
Description: "Disable a plugin in this guild",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "plugin",
|
||||
Description: "The name of the plugin to disable",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
s.AddHandler(handleAutocomplete)
|
||||
s.AddHandler(handlePluginEvent)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load recursively loads plugins from the given directory.
|
||||
func Load(dir string) error {
|
||||
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() || filepath.Ext(path) != ".js" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vm := lockableRuntime{&sync.Mutex{}, goja.New()}
|
||||
vm.SetFieldNameMapper(lowerCamelNameMapper{})
|
||||
|
||||
api := &owobotAPI{vm: vm, path: path}
|
||||
|
||||
err = errors.Join(
|
||||
vm.GlobalObject().Set("owobot", api),
|
||||
vm.GlobalObject().Set("discord", builtins.Constants),
|
||||
vm.GlobalObject().Set("print", fmt.Println),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = vm.RunScript(path, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !api.PluginInfo.IsValid() {
|
||||
log.Warn("Plugin info not provided, skipping.").Str("path", path).Send()
|
||||
return nil
|
||||
}
|
||||
|
||||
prev, _ := db.GetPlugin(api.PluginInfo.Name)
|
||||
|
||||
err = db.AddPlugin(api.PluginInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = builtins.Register(vm.Runtime, api.PluginInfo.Name, api.PluginInfo.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Plugins = append(Plugins, Plugin{
|
||||
Info: api.PluginInfo,
|
||||
Commands: api.Commands,
|
||||
VM: vm,
|
||||
api: api,
|
||||
})
|
||||
|
||||
if api.Init != nil {
|
||||
callableInit, ok := goja.AssertFunction(api.Init)
|
||||
if !ok {
|
||||
log.Warn("Init value is not callable, ignoring.").Str("plugin", api.PluginInfo.Name).Send()
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = callableInit(vm.ToValue(api), vm.ToValue(prev))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s init: %w", api.PluginInfo.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
@ -305,7 +305,7 @@ func updatePollUnfinished(s *discordgo.Session, msgID, channelID string) error {
|
||||
ID: msgID,
|
||||
Channel: channelID,
|
||||
Content: &content,
|
||||
Components: []discordgo.MessageComponent{
|
||||
Components: &[]discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{Components: []discordgo.MessageComponent{
|
||||
discordgo.Button{
|
||||
Label: "Add Option",
|
||||
|
@ -264,13 +264,13 @@ func updateReactionRoleCategoryMsg(s *discordgo.Session, channelID, category str
|
||||
_, err = s.ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||
Channel: channelID,
|
||||
ID: rrc.MsgID,
|
||||
Embeds: []*discordgo.MessageEmbed{
|
||||
Embeds: &[]*discordgo.MessageEmbed{
|
||||
{
|
||||
Title: rrc.Name,
|
||||
Description: sb.String(),
|
||||
},
|
||||
},
|
||||
Components: components,
|
||||
Components: &components,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
@ -78,6 +78,10 @@ func Open(s *discordgo.Session, guildID string, user, executor *discordgo.User)
|
||||
return "", fmt.Errorf("ticket already exists for %s at <#%s>", user.Mention(), channelID)
|
||||
}
|
||||
|
||||
if executor == nil {
|
||||
executor = s.State.User
|
||||
}
|
||||
|
||||
guild, err := db.GuildByID(guildID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -131,6 +135,10 @@ func Close(s *discordgo.Session, guildID string, user, executor *discordgo.User)
|
||||
return err
|
||||
}
|
||||
|
||||
if executor == nil {
|
||||
executor = s.State.User
|
||||
}
|
||||
|
||||
guild, err := db.GuildByID(guildID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
7
main.go
7
main.go
@ -33,6 +33,7 @@ import (
|
||||
"go.elara.ws/owobot/internal/systems/eventlog"
|
||||
"go.elara.ws/owobot/internal/systems/guilds"
|
||||
"go.elara.ws/owobot/internal/systems/members"
|
||||
"go.elara.ws/owobot/internal/systems/plugins"
|
||||
"go.elara.ws/owobot/internal/systems/polls"
|
||||
"go.elara.ws/owobot/internal/systems/reactions"
|
||||
"go.elara.ws/owobot/internal/systems/roles"
|
||||
@ -85,6 +86,11 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
err = plugins.Load(cfg.PluginDir)
|
||||
if err != nil {
|
||||
log.Error("Error running plugin file").Err(err).Send()
|
||||
}
|
||||
|
||||
initSystems(
|
||||
s,
|
||||
starboard.Init,
|
||||
@ -97,6 +103,7 @@ func main() {
|
||||
reactions.Init,
|
||||
roles.Init,
|
||||
about.Init,
|
||||
plugins.Init,
|
||||
commands.Init, // The commands system should always go last
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
token = "CHANGE ME"
|
||||
db_path = "/etc/owobot/owobot.db"
|
||||
plugin_dir = "/etc/owobot/plugins"
|
||||
|
||||
[activity]
|
||||
type = -1
|
||||
|
Loading…
Reference in New Issue
Block a user