Compare commits
15 Commits
b73ae041c6
...
c18c81138a
Author | SHA1 | Date |
---|---|---|
bitspill | c18c81138a | |
diamondburned | 19518a0844 | |
diamondburned | dbc4ae8978 | |
Benbebop | 10d3626429 | |
diamondburned | ff26fded59 | |
Benbebop | 3bbdae9bc1 | |
diamondburned | 966d966897 | |
diamondburned | 1782301df9 | |
diamondburned | 796b798f6a | |
diamondburned | f7f228a848 | |
diamondburned | dffb6400fe | |
diamondburned | 9809321f6f | |
dependabot[bot] | fce16ffb87 | |
dependabot[bot] | 12ddfba102 | |
bitspill | 0e24fffde0 |
|
@ -10,7 +10,8 @@ require (
|
|||
require (
|
||||
github.com/gorilla/schema v1.2.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
)
|
||||
|
|
|
@ -1,38 +1,53 @@
|
|||
github.com/diamondburned/arikawa/v3 v3.0.0-rc.6 h1:lALIGxvojmcO/T6zeO53OzFXsuVWhXZ0Q/lYPehiKg0=
|
||||
github.com/diamondburned/arikawa/v3 v3.0.0-rc.6/go.mod h1:5jBSNnp82Z/EhsKa6Wk9FsOqSxfVkNZDTDBPOj47LpY=
|
||||
github.com/diamondburned/oggreader v0.0.0-20201118014549-87df9534b647 h1:TJWvffl1cMLzSOvw8Wv3CQicuU9NaKDKXvBfh5T9W00=
|
||||
github.com/diamondburned/oggreader v0.0.0-20201118014549-87df9534b647/go.mod h1:xEJuvlmPx1wBKUWkx+MUp1ULSMQwSM9FS+bnFJhPQkk=
|
||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
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/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211001092434-39dca1131b70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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=
|
||||
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=
|
||||
|
|
|
@ -20,7 +20,9 @@ func (c *Client) CurrentApplication() (*discord.Application, error) {
|
|||
}
|
||||
|
||||
// https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
|
||||
// https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands
|
||||
type CreateCommandData struct {
|
||||
ID discord.CommandID `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
NameLocalizations discord.StringLocales `json:"name_localizations,omitempty"`
|
||||
Description string `json:"description"`
|
||||
|
|
|
@ -10,9 +10,10 @@ import (
|
|||
|
||||
// Router is a router for slash commands. A zero-value Router is a valid router.
|
||||
type Router struct {
|
||||
nodes map[string]routeNode
|
||||
mws []Middleware
|
||||
stack []*Router
|
||||
nodes map[string]routeNode
|
||||
mws []Middleware
|
||||
parent *Router // parent router, if any
|
||||
groups []Router // next routers to check, if any
|
||||
}
|
||||
|
||||
type routeNode interface {
|
||||
|
@ -44,9 +45,6 @@ func NewRouter() *Router {
|
|||
}
|
||||
|
||||
func (r *Router) init() {
|
||||
if r.stack == nil {
|
||||
r.stack = []*Router{r}
|
||||
}
|
||||
if r.nodes == nil {
|
||||
r.nodes = make(map[string]routeNode, 4)
|
||||
}
|
||||
|
@ -75,7 +73,7 @@ func (r *Router) Use(mws ...Middleware) {
|
|||
// parent command of the given name.
|
||||
func (r *Router) Sub(name string, f func(r *Router)) {
|
||||
sub := NewRouter()
|
||||
sub.stack = append(append([]*Router(nil), r.stack...), sub)
|
||||
sub.parent = r
|
||||
f(sub)
|
||||
|
||||
r.add(name, routeNodeSub{sub})
|
||||
|
@ -92,6 +90,39 @@ func (r *Router) AddFunc(name string, f CommandHandlerFunc) {
|
|||
r.Add(name, f)
|
||||
}
|
||||
|
||||
// Group creates a subrouter that handles certain commands within the parent
|
||||
// command. This is useful for assigning middlewares to a group of commands that
|
||||
// belong to the same parent command.
|
||||
//
|
||||
// For example, consider the following:
|
||||
//
|
||||
// r := cmdroute.NewRouter()
|
||||
// r.Group(func(r *cmdroute.Router) {
|
||||
// r.Use(cmdroute.Deferrable(client, cmdroute.DeferOpts{}))
|
||||
// r.Add("foo", handleFoo)
|
||||
// })
|
||||
// r.Add("bar", handleBar)
|
||||
//
|
||||
// In this example, the middleware is only applied to the "foo" command, and not
|
||||
// the "bar" command.
|
||||
func (r *Router) Group(f func(r *Router)) {
|
||||
f(r.With())
|
||||
}
|
||||
|
||||
// With is similar to Group, but it returns a new router instead of calling a
|
||||
// function with a new router. This is useful for chaining middlewares once,
|
||||
// such as:
|
||||
//
|
||||
// r := cmdroute.NewRouter()
|
||||
// r.With(cmdroute.Deferrable(client, cmdroute.DeferOpts{})).Add("foo", handleFoo)
|
||||
func (r *Router) With(mws ...Middleware) *Router {
|
||||
r.groups = append(r.groups, Router{})
|
||||
sub := &r.groups[len(r.groups)-1]
|
||||
sub.parent = r
|
||||
sub.mws = append(sub.mws, mws...)
|
||||
return sub
|
||||
}
|
||||
|
||||
// HandleInteraction implements webhook.InteractionHandler. It only handles
|
||||
// events of type CommandInteraction, otherwise nil is returned.
|
||||
func (r *Router) HandleInteraction(ev *discord.InteractionEvent) *api.InteractionResponse {
|
||||
|
@ -113,11 +144,11 @@ func (r *Router) callHandler(ev *discord.InteractionEvent, fn InteractionHandler
|
|||
// Apply middlewares, parent last, first one added last. This ensures that
|
||||
// when we call the handler, the middlewares are applied in the order they
|
||||
// were added.
|
||||
for i := len(r.stack) - 1; i >= 0; i-- {
|
||||
r := r.stack[i]
|
||||
for j := len(r.mws) - 1; j >= 0; j-- {
|
||||
h = r.mws[j](h)
|
||||
for r != nil {
|
||||
for i := len(r.mws) - 1; i >= 0; i-- {
|
||||
h = r.mws[i](h)
|
||||
}
|
||||
r = r.parent
|
||||
}
|
||||
|
||||
return h.HandleInteraction(context.Background(), ev)
|
||||
|
@ -162,7 +193,27 @@ type handlerData struct {
|
|||
data discord.CommandInteractionOption
|
||||
}
|
||||
|
||||
// findCommandHandler finds the command handler for the given command name.
|
||||
// It checks the current router and its groups.
|
||||
func (r *Router) findCommandHandler(ev *discord.InteractionEvent, data discord.CommandInteractionOption) (handlerData, bool) {
|
||||
found, ok := r.findCommandHandlerOnce(ev, data)
|
||||
if ok {
|
||||
return found, true
|
||||
}
|
||||
|
||||
for _, sub := range r.groups {
|
||||
found, ok = sub.findCommandHandlerOnce(ev, data)
|
||||
if ok {
|
||||
return found, true
|
||||
}
|
||||
}
|
||||
|
||||
return handlerData{}, false
|
||||
}
|
||||
|
||||
// findCommandHandlerOnce finds the command handler for the given command name.
|
||||
// It only checks the current router and not its groups.
|
||||
func (r *Router) findCommandHandlerOnce(ev *discord.InteractionEvent, data discord.CommandInteractionOption) (handlerData, bool) {
|
||||
node, ok := r.nodes[data.Name]
|
||||
if !ok {
|
||||
return handlerData{}, false
|
||||
|
|
|
@ -165,34 +165,45 @@ func TestRouter(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("middlewares", func(t *testing.T) {
|
||||
var stack []string
|
||||
pushStack := func(s string) Middleware {
|
||||
return func(next InteractionHandler) InteractionHandler {
|
||||
return InteractionHandlerFunc(func(ctx context.Context, ev *discord.InteractionEvent) *api.InteractionResponse {
|
||||
stack = append(stack, s)
|
||||
return next.HandleInteraction(ctx, ev)
|
||||
})
|
||||
}
|
||||
}
|
||||
var stack middlewareStacker
|
||||
|
||||
r := NewRouter()
|
||||
r.Use(pushStack("root1"))
|
||||
r.Use(pushStack("root2"))
|
||||
r.Use(stack.pusher("root1"))
|
||||
r.Use(stack.pusher("root2"))
|
||||
r.Sub("test", func(r *Router) {
|
||||
r.Use(pushStack("sub1.1"))
|
||||
r.Use(pushStack("sub1.2"))
|
||||
r.Sub("sub1", func(r *Router) {
|
||||
r.Use(pushStack("sub2.1"))
|
||||
r.Use(pushStack("sub2.2"))
|
||||
r.Add("sub2", assertHandler(t, mockOptions))
|
||||
// We put test 1 at the start, but test 2 at the end.
|
||||
// The order should be preserved.
|
||||
r.Use(stack.pusher("test.1"))
|
||||
|
||||
// unused
|
||||
r.Group(func(r *Router) {
|
||||
r.Use(stack.pusher("test.3"))
|
||||
})
|
||||
|
||||
// unused
|
||||
r.With(stack.pusher("test.4"))
|
||||
|
||||
r.Group(func(r *Router) {
|
||||
r.Use(stack.pusher("test.5"))
|
||||
|
||||
r.Sub("sub", func(r *Router) {
|
||||
r.Use(stack.pusher("test.sub.1"))
|
||||
r.Use(stack.pusher("test.sub.2"))
|
||||
|
||||
r.Add("sub2", assertHandler(t, mockOptions))
|
||||
})
|
||||
})
|
||||
|
||||
// Test 2 goes here.
|
||||
r.Use(stack.pusher("test.2"))
|
||||
})
|
||||
|
||||
r.HandleInteraction(newInteractionEvent(&discord.CommandInteraction{
|
||||
ID: 4,
|
||||
Name: "test",
|
||||
Options: []discord.CommandInteractionOption{
|
||||
{
|
||||
Name: "sub1",
|
||||
Name: "sub",
|
||||
Type: discord.SubcommandGroupOptionType,
|
||||
Options: []discord.CommandInteractionOption{
|
||||
{
|
||||
|
@ -205,23 +216,15 @@ func TestRouter(t *testing.T) {
|
|||
},
|
||||
}))
|
||||
|
||||
expects := []string{
|
||||
stack.expect(t, []string{
|
||||
"root1",
|
||||
"root2",
|
||||
"sub1.1",
|
||||
"sub1.2",
|
||||
"sub2.1",
|
||||
"sub2.2",
|
||||
}
|
||||
if len(stack) != len(expects) {
|
||||
t.Fatalf("expected stack to have %d elements, got %d", len(expects), len(stack))
|
||||
}
|
||||
|
||||
for i := range expects {
|
||||
if stack[i] != expects[i] {
|
||||
t.Fatalf("expected stack[%d] to be %q, got %q", i, expects[i], stack[i])
|
||||
}
|
||||
}
|
||||
"test.1",
|
||||
"test.2",
|
||||
"test.5",
|
||||
"test.sub.1",
|
||||
"test.sub.2",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("deferred", func(t *testing.T) {
|
||||
|
@ -335,6 +338,7 @@ var mockOptions = []discord.CommandInteractionOption{
|
|||
},
|
||||
}
|
||||
|
||||
// assertHandler asserts that the given options are equal to the expected options.
|
||||
func assertHandler(t *testing.T, opts discord.CommandInteractionOptions) CommandHandler {
|
||||
return CommandHandlerFunc(func(ctx context.Context, data CommandData) *api.InteractionResponseData {
|
||||
if len(data.Options) != len(opts) {
|
||||
|
@ -410,3 +414,25 @@ func strInteractionResp(resp *api.InteractionResponse) string {
|
|||
}
|
||||
return fmt.Sprintf("%d:%#v", resp.Type, resp.Data)
|
||||
}
|
||||
|
||||
type middlewareStacker []string
|
||||
|
||||
func (m *middlewareStacker) pusher(s string) Middleware {
|
||||
return func(next InteractionHandler) InteractionHandler {
|
||||
return InteractionHandlerFunc(func(ctx context.Context, ev *discord.InteractionEvent) *api.InteractionResponse {
|
||||
*m = append(*m, s)
|
||||
return next.HandleInteraction(ctx, ev)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m middlewareStacker) expect(t *testing.T, expects []string) {
|
||||
if len(m) != len(expects) {
|
||||
t.Fatalf("expected stack to have %d elements, got %d: %v", len(expects), len(m), m)
|
||||
}
|
||||
for i := range expects {
|
||||
if m[i] != expects[i] {
|
||||
t.Fatalf("expected stack[%d] to be %q, got %q", i, expects[i], m[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,6 +310,9 @@ type ThreadMetadata struct {
|
|||
// Invitable specifies whether non-moderators can add other
|
||||
// non-moderators to a thread; only available on private threads.
|
||||
Invitable bool `json:"invitable,omitempty"`
|
||||
// CreateTimestamp is the timestamp when the thread was created; only
|
||||
// populated for threads created after 2022-01-09.
|
||||
CreateTimestamp *Timestamp `json:"thread_metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ThreadMember struct {
|
||||
|
|
|
@ -280,6 +280,8 @@ func (u *UnknownCommandOption) UnmarshalJSON(b []byte) error {
|
|||
u.data = &MentionableOption{}
|
||||
case NumberOptionType:
|
||||
u.data = &NumberOption{}
|
||||
case AttachmentOptionType:
|
||||
u.data = &AttachmentOption{}
|
||||
default:
|
||||
// Copy the blob of bytes into a new slice.
|
||||
u.raw = append(json.Raw(nil), b...)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package discord
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://discord.com/developers/docs/resources/guild#guild-object
|
||||
type Guild struct {
|
||||
|
@ -363,6 +366,15 @@ func (r Role) IconURLWithType(t ImageType) string {
|
|||
return "https://cdn.discordapp.com/role-icons/" + r.ID.String() + "/" + t.format(r.Icon)
|
||||
}
|
||||
|
||||
// SortRolesByPosition sorts the roles by their position.
|
||||
// Roles with a higher position will be first in the slice, similar to how
|
||||
// Discord sorts roles in the client.
|
||||
func SortRolesByPosition(roles []Role) {
|
||||
sort.Slice(roles, func(i, j int) bool {
|
||||
return roles[i].Position > roles[j].Position
|
||||
})
|
||||
}
|
||||
|
||||
// https://discord.com/developers/docs/resources/guild#guild-member-object
|
||||
//
|
||||
// The field user won't be included in the member object attached to
|
||||
|
|
|
@ -540,16 +540,16 @@ var optionSupportedSnowflakeTypes = map[reflect.Type]CommandOptionType{
|
|||
}
|
||||
|
||||
var optionKindMap = map[reflect.Kind]CommandOptionType{
|
||||
reflect.Int: NumberOptionType,
|
||||
reflect.Int8: NumberOptionType,
|
||||
reflect.Int16: NumberOptionType,
|
||||
reflect.Int32: NumberOptionType,
|
||||
reflect.Int64: NumberOptionType,
|
||||
reflect.Uint: NumberOptionType,
|
||||
reflect.Uint8: NumberOptionType,
|
||||
reflect.Uint16: NumberOptionType,
|
||||
reflect.Uint32: NumberOptionType,
|
||||
reflect.Uint64: NumberOptionType,
|
||||
reflect.Int: IntegerOptionType,
|
||||
reflect.Int8: IntegerOptionType,
|
||||
reflect.Int16: IntegerOptionType,
|
||||
reflect.Int32: IntegerOptionType,
|
||||
reflect.Int64: IntegerOptionType,
|
||||
reflect.Uint: IntegerOptionType,
|
||||
reflect.Uint8: IntegerOptionType,
|
||||
reflect.Uint16: IntegerOptionType,
|
||||
reflect.Uint32: IntegerOptionType,
|
||||
reflect.Uint64: IntegerOptionType,
|
||||
reflect.Float32: NumberOptionType,
|
||||
reflect.Float64: NumberOptionType,
|
||||
reflect.String: StringOptionType,
|
||||
|
|
|
@ -170,6 +170,8 @@ func (p Permissions) Add(perm Permissions) Permissions {
|
|||
return p | perm
|
||||
}
|
||||
|
||||
// CalcOverrides calculates the permissions for a member in the given channel.
|
||||
// Most of the time, you should use state.State.Permissions instead.
|
||||
func CalcOverrides(
|
||||
guild Guild, channel Channel, member Member, roles []Role) Permissions {
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ func init() {
|
|||
func() ws.Event { return new(UserNoteUpdateEvent) },
|
||||
func() ws.Event { return new(RelationshipAddEvent) },
|
||||
func() ws.Event { return new(RelationshipRemoveEvent) },
|
||||
func() ws.Event { return new(ConversationSummaryUpdateEvent) },
|
||||
func() ws.Event { return new(ReadyEvent) },
|
||||
func() ws.Event { return new(ReadySupplementalEvent) },
|
||||
func() ws.Event { return new(GuildScheduledEventCreateEvent) },
|
||||
|
@ -453,6 +454,12 @@ func (*RelationshipRemoveEvent) Op() ws.OpCode { return dispatchOp }
|
|||
// EventType implements Event.
|
||||
func (*RelationshipRemoveEvent) EventType() ws.EventType { return "RELATIONSHIP_REMOVE" }
|
||||
|
||||
// Op implements Event. It always returns 0.
|
||||
func (*ConversationSummaryUpdateEvent) Op() ws.OpCode { return dispatchOp }
|
||||
|
||||
// EventType implements Event.
|
||||
func (*ConversationSummaryUpdateEvent) EventType() ws.EventType { return "CONVERSATION_SUMMARY_UPDATE" }
|
||||
|
||||
// Op implements Event. It always returns 0.
|
||||
func (*ReadyEvent) Op() ws.OpCode { return dispatchOp }
|
||||
|
||||
|
|
|
@ -667,6 +667,36 @@ type RelationshipRemoveEvent struct {
|
|||
discord.Relationship
|
||||
}
|
||||
|
||||
// ConversationSummaryUpdateEvent is a dispatch event. It is undocumented.
|
||||
type ConversationSummaryUpdateEvent struct {
|
||||
ChannelID discord.ChannelID `json:"channel_id"`
|
||||
GuildID discord.GuildID `json:"guild_id,omitempty"`
|
||||
Summaries []ConversationSummary `json:"summaries"`
|
||||
}
|
||||
|
||||
// ConversationSummary is a structure for ConversationSummaryUpdateEvent.
|
||||
// It is undocumented.
|
||||
type ConversationSummary struct {
|
||||
Unsafe bool `json:"unsafe"`
|
||||
// Topic is the topic of the conversation.
|
||||
Topic string `json:"topic"`
|
||||
// ShortSummary is a short summary of the conversation.
|
||||
// It is in sentence form.
|
||||
ShortSummary string `json:"summ_short"`
|
||||
// People is a list of user IDs in the conversation.
|
||||
People []discord.UserID `json:"people"`
|
||||
// StartID is the ID of the first message in the conversation.
|
||||
StartID discord.MessageID `json:"start_id"`
|
||||
// EndID is the ID of the last message in the conversation.
|
||||
EndID discord.MessageID `json:"end_id"`
|
||||
// MessageIDs is a list of message IDs in the conversation.
|
||||
MessageIDs []discord.MessageID `json:"message_ids"`
|
||||
// ID is some kind of ID that identifies the conversation?
|
||||
ID discord.Snowflake `json:"id"`
|
||||
// Count is the number of messages in the conversation.
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// ReadyEvent is a dispatch event for READY.
|
||||
//
|
||||
// https://discord.com/developers/docs/topics/gateway#ready
|
||||
|
|
2
go.mod
2
go.mod
|
@ -5,6 +5,6 @@ go 1.16
|
|||
require (
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -5,32 +5,42 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
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/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-20210615035016-665e8c7367d1/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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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=
|
||||
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=
|
||||
|
|
|
@ -280,6 +280,46 @@ func MemberColor(m *discord.Member, role func(discord.RoleID) *discord.Role) (di
|
|||
return c, pos != -1
|
||||
}
|
||||
|
||||
// SortedRoles returns a list of roles sorted by their position.
|
||||
// The roles are fetched using (*State).Roles.
|
||||
func (s *State) SortedRoles(guildID discord.GuildID) ([]discord.Role, error) {
|
||||
roles, err := s.Roles(guildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
discord.SortRolesByPosition(roles)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// MemberRoles returns a list of roles that the given member has.
|
||||
// The returned roles are sorted by their position.
|
||||
func (s *State) MemberRoles(guildID discord.GuildID, userID discord.UserID) ([]discord.Role, error) {
|
||||
roles, err := s.Roles(guildID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get roles for guild: %w", err)
|
||||
}
|
||||
|
||||
member, err := s.Member(guildID, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get member: %w", err)
|
||||
}
|
||||
|
||||
filtered := make([]discord.Role, 0, len(member.RoleIDs))
|
||||
roleSearch:
|
||||
for _, roleID := range member.RoleIDs {
|
||||
for _, role := range roles {
|
||||
if role.ID == roleID {
|
||||
filtered = append(filtered, role)
|
||||
continue roleSearch
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find role %d for member", roleID)
|
||||
}
|
||||
|
||||
discord.SortRolesByPosition(filtered)
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
// Permissions gets the user's permissions in the given channel. If the channel
|
||||
|
@ -667,17 +707,25 @@ func (s *State) Messages(channelID discord.ChannelID, limit uint) ([]discord.Mes
|
|||
return storeMessages[:limit], nil
|
||||
}
|
||||
|
||||
// Decrease the limit, if we aren't fetching all messages.
|
||||
if limit > 0 {
|
||||
limit -= uint(len(storeMessages))
|
||||
fetchLimit := limit
|
||||
|
||||
// If the user is requesting less than MaxMessages, then increase the limit
|
||||
// to at least that so that channels don't accidentally get marked as tiny.
|
||||
if fetchLimit < uint(s.MaxMessages()) {
|
||||
fetchLimit = uint(s.MaxMessages())
|
||||
}
|
||||
|
||||
var before discord.MessageID = 0
|
||||
// Decrease the fetchLimit, if we aren't fetching all messages.
|
||||
if fetchLimit > 0 {
|
||||
fetchLimit -= uint(len(storeMessages))
|
||||
}
|
||||
|
||||
var before discord.MessageID
|
||||
if len(storeMessages) > 0 {
|
||||
before = storeMessages[len(storeMessages)-1].ID
|
||||
}
|
||||
|
||||
apiMessages, err := s.Session.MessagesBefore(channelID, before, limit)
|
||||
apiMessages, err := s.Session.MessagesBefore(channelID, before, fetchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -690,6 +738,9 @@ func (s *State) Messages(channelID discord.ChannelID, limit uint) ([]discord.Mes
|
|||
}
|
||||
|
||||
if len(apiMessages) == 0 {
|
||||
if limit > 0 && len(storeMessages) > int(limit) {
|
||||
return storeMessages[:limit], nil
|
||||
}
|
||||
return storeMessages, nil
|
||||
}
|
||||
|
||||
|
@ -724,7 +775,12 @@ func (s *State) Messages(channelID discord.ChannelID, limit uint) ([]discord.Mes
|
|||
}
|
||||
}
|
||||
|
||||
return append(storeMessages, apiMessages...), nil
|
||||
msgs := append(storeMessages, apiMessages...)
|
||||
if limit > 0 && len(msgs) > int(limit) {
|
||||
msgs = msgs[:limit]
|
||||
}
|
||||
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
////
|
||||
|
|
|
@ -38,32 +38,48 @@ func New() *Handler {
|
|||
// Call calls all handlers with the given event. This is an internal method; use
|
||||
// with care.
|
||||
func (h *Handler) Call(ev interface{}) {
|
||||
v := reflect.ValueOf(ev)
|
||||
t := reflect.TypeOf(ev)
|
||||
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
all := h.AllCallersForType(t)
|
||||
all(func(caller Caller) bool {
|
||||
caller.Call(v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
typedHandlers := h.events[t].Entries
|
||||
anyHandlers := h.events[nil].Entries
|
||||
// AllCallersForType returns all callers for the given event type. This is an
|
||||
// internal method that is rarely useful for external use and should be used
|
||||
// with care.
|
||||
func (h *Handler) AllCallersForType(t reflect.Type) func(yield func(Caller) bool) {
|
||||
return func(yield func(Caller) bool) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
if len(typedHandlers) == 0 && len(anyHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
typedHandlers := h.events[t].Entries
|
||||
anyHandlers := h.events[nil].Entries
|
||||
|
||||
v := reflect.ValueOf(ev)
|
||||
|
||||
for _, entry := range typedHandlers {
|
||||
if entry.isInvalid() {
|
||||
continue
|
||||
if len(typedHandlers) == 0 && len(anyHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
entry.Call(v)
|
||||
}
|
||||
|
||||
for _, entry := range anyHandlers {
|
||||
if entry.isInvalid() || entry.not(t) {
|
||||
continue
|
||||
for _, entry := range typedHandlers {
|
||||
if entry.isInvalid() {
|
||||
continue
|
||||
}
|
||||
if !yield(entry) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, entry := range anyHandlers {
|
||||
if entry.isInvalid() || entry.not(t) {
|
||||
continue
|
||||
}
|
||||
if !yield(entry) {
|
||||
return
|
||||
}
|
||||
}
|
||||
entry.Call(v)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +255,10 @@ func (h *Handler) addHandler(fn interface{}, sync bool) (rm func(), err error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Caller is an interface that can be used to call a handler.
|
||||
// It directly accepts a reflect.Value, which is the event.
|
||||
type Caller interface{ Call(ev reflect.Value) }
|
||||
|
||||
type handler struct {
|
||||
event reflect.Type // underlying type; arg0 or chan underlying type
|
||||
callback reflect.Value
|
||||
|
@ -248,6 +268,8 @@ type handler struct {
|
|||
isOnce bool
|
||||
}
|
||||
|
||||
var _ Caller = (*handler)(nil)
|
||||
|
||||
// newHandler reflects either a channel or a function into a handler. A function
|
||||
// must only have a single argument being the event and no return, and a channel
|
||||
// must have the event type as the underlying type.
|
||||
|
|
Loading…
Reference in New Issue