Skip to content

Commit

Permalink
implement command store [fix #2]
Browse files Browse the repository at this point in the history
  • Loading branch information
zekroTJA committed Oct 5, 2021
1 parent 4bd0b5e commit 96e3f3c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 27 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
# Dependency directories (remove the comment below to include it)
# vendor/

.vscode/
.vscode/
.commandCache.json
20 changes: 14 additions & 6 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ import (
"github.com/bwmarrin/discordgo"
"github.com/zekrotja/ken"
"github.com/zekrotja/ken/examples/basic/commands"
"github.com/zekrotja/ken/store"
)

func must(err error) {
if err != nil {
panic(err)
}
}

func main() {
token := os.Getenv("TOKEN")

Expand All @@ -19,15 +26,16 @@ func main() {
}
defer session.Close()

k := ken.New(session)
k.RegisterCommands(new(commands.TestCommand))
k, err := ken.New(session, ken.Options{
CommandStore: store.NewDefault(),
})
must(err)

must(k.RegisterCommands(new(commands.TestCommand)))

defer k.Unregister()

err = session.Open()
if err != nil {
panic(err)
}
must(session.Open())

sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
Expand Down
27 changes: 18 additions & 9 deletions examples/middlewares/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ import (
"github.com/zekrotja/ken"
"github.com/zekrotja/ken/examples/middlewares/commands"
"github.com/zekrotja/ken/examples/middlewares/middlewares"
"github.com/zekrotja/ken/store"
)

func must(err error) {
if err != nil {
panic(err)
}
}

func main() {
token := os.Getenv("TOKEN")

Expand All @@ -20,20 +27,22 @@ func main() {
}
defer session.Close()

k := ken.New(session)
k.RegisterCommands(
k, err := ken.New(session, ken.Options{
CommandStore: store.NewDefault(),
})
must(err)

must(k.RegisterCommands(
new(commands.KickCommand),
)
k.RegisterMiddlewares(
))

must(k.RegisterMiddlewares(
new(middlewares.PermissionsMiddleware),
)
))

defer k.Unregister()

err = session.Open()
if err != nil {
panic(err)
}
must(session.Open())

sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
Expand Down
77 changes: 66 additions & 11 deletions ken.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/bwmarrin/discordgo"
"github.com/zekrotja/ken/state"
"github.com/zekrotja/ken/store"
)

// EmbedColors lets you define custom colors for embeds.
Expand All @@ -26,6 +27,9 @@ type Options struct {
// When not specified, the default discordgo state
// manager is used.
State state.State
// CommandStore specifies a storage instance to
// cache created commands.
CommandStore store.CommandStore
// DependencyProvider can be used to inject dependencies
// to be used in a commands or middlewares Ctx by
// a string key.
Expand All @@ -48,7 +52,7 @@ type Ken struct {

cmdsLock sync.RWMutex
cmds map[string]Command
cmdIDs []string
idcache map[string]string
ctxPool sync.Pool

mwBefore []MiddlewareBefore
Expand All @@ -75,11 +79,11 @@ var defaultOptions = Options{
//
// If no options are passed, default parameters
// will be applied.
func New(s *discordgo.Session, options ...Options) (k *Ken) {
func New(s *discordgo.Session, options ...Options) (k *Ken, err error) {
k = &Ken{
s: s,
cmds: make(map[string]Command),
cmdIDs: make([]string, 0),
s: s,
cmds: make(map[string]Command),
idcache: make(map[string]string),
ctxPool: sync.Pool{
New: func() interface{} {
return newCtx()
Expand All @@ -98,6 +102,9 @@ func New(s *discordgo.Session, options ...Options) (k *Ken) {
if o.State != nil {
k.opt.State = o.State
}
if o.CommandStore != nil {
k.opt.CommandStore = o.CommandStore
}
if o.OnSystemError != nil {
k.opt.OnSystemError = o.OnSystemError
}
Expand All @@ -112,6 +119,13 @@ func New(s *discordgo.Session, options ...Options) (k *Ken) {
}
}

if k.opt.CommandStore != nil {
k.idcache, err = k.opt.CommandStore.Load()
if err != nil {
return
}
}

k.s.AddHandler(k.onReady)
k.s.AddHandler(k.onInteractionCreate)

Expand Down Expand Up @@ -163,12 +177,19 @@ func (k *Ken) RegisterMiddlewares(mws ...interface{}) (err error) {
// Unregister should be called to cleanly unregister
// all registered slash commands from the discord
// backend.
//
// This can be skipped if you are using
// a CommandStore.
func (k *Ken) Unregister() (err error) {
if len(k.idcache) == 0 {
return
}

self, err := k.opt.State.SelfUser(k.s)
if err != nil {
return
}
for _, id := range k.cmdIDs {
for _, id := range k.idcache {
if err = k.s.ApplicationCommandDelete(self.ID, "", id); err != nil {
k.opt.OnSystemError("command unregister", err)
}
Expand Down Expand Up @@ -213,12 +234,46 @@ func (k *Ken) onReady(s *discordgo.Session, e *discordgo.Ready) {
k.cmdsLock.RLock()
defer k.cmdsLock.RUnlock()

for _, cmd := range k.cmds {
ccmd, err := s.ApplicationCommandCreate(e.User.ID, "", toApplicationCommand(cmd))
if err != nil {
k.opt.OnSystemError("command registration", err)
var (
ccmd *discordgo.ApplicationCommand
err error
update = []*discordgo.ApplicationCommand{}
)

for name, cmd := range k.cmds {
if _, ok := k.idcache[name]; ok {
acmd := toApplicationCommand(cmd)
update = append(update, acmd)
} else {
k.cmdIDs = append(k.cmdIDs, ccmd.ID)
ccmd, err = s.ApplicationCommandCreate(e.User.ID, "", toApplicationCommand(cmd))
if err != nil {
k.opt.OnSystemError("command registration", err)
} else {
k.idcache[name] = ccmd.ID
}
}
}

if len(update) > 0 {
_, err = s.ApplicationCommandBulkOverwrite(e.User.ID, "", update)
if err != nil {
k.opt.OnSystemError("command update", err)
}
}

for name, id := range k.idcache {
if _, ok := k.cmds[name]; !ok {
err = s.ApplicationCommandDelete(e.User.ID, "", id)
if err != nil {
k.opt.OnSystemError("command delete", err)
}
}
}

if k.opt.CommandStore != nil {
err = k.opt.CommandStore.Store(k.idcache)
if err != nil {
k.opt.OnSystemError("idcache storage", err)
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions store/local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package store

import (
"encoding/json"
"os"
)

// LocalCommandStore implements CommandStore for a
// local file as storage device.
type LocalCommandStore struct {
loc string
}

var _ CommandStore = (*LocalCommandStore)(nil)

// NewLocalCommandStore creates a new instance of
// LocalCommandStore with the passed file location
// as stoage destination.
func NewLocalCommandStore(loc string) *LocalCommandStore {
return &LocalCommandStore{loc}
}

// NewDefault returns a new LocalCommandStore with
// default file location (".commandCache.json").
func NewDefault() *LocalCommandStore {
return NewLocalCommandStore(".commandCache.json")
}

func (lcs *LocalCommandStore) Store(cmds map[string]string) (err error) {
f, err := os.Create(lcs.loc)
if err != nil {
return
}
defer f.Close()
err = json.NewEncoder(f).Encode(cmds)
return
}

func (lcs *LocalCommandStore) Load() (cmds map[string]string, err error) {
cmds = map[string]string{}
f, err := os.Open(lcs.loc)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return
}
defer f.Close()
err = json.NewDecoder(f).Decode(&cmds)
return
}
13 changes: 13 additions & 0 deletions store/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package store

// CommandStore allows to store and load registered
// commands so that they can be updated instead
// of deleted and re-created on restarts.
type CommandStore interface {
// Store stores the passed commands map.
Store(cmds map[string]string) error
// Load retrieves a stored commands map or
// an empty map, if no store was applied
// before.
Load() (cmds map[string]string, err error)
}

0 comments on commit 96e3f3c

Please sign in to comment.