From 3ee307b788a2f43079743383c8a686cf6abe5c6c Mon Sep 17 00:00:00 2001 From: "diamondburned (Forefront)" Date: Wed, 8 Apr 2020 21:25:50 -0700 Subject: [PATCH] Bot: Added Usager, improved help --- bot/arguments.go | 21 ++++++++++- bot/ctx.go | 31 ++++++----------- bot/extras/arguments/emoji.go | 4 +++ bot/extras/arguments/link.go | 62 +++++++++++++++++++++++++++++++++ bot/extras/arguments/mention.go | 13 +++---- bot/subcommand.go | 19 ++++++---- state/state.go | 4 +-- 7 files changed, 113 insertions(+), 41 deletions(-) create mode 100644 bot/extras/arguments/link.go diff --git a/bot/arguments.go b/bot/arguments.go index 0cae6df..09e8202 100644 --- a/bot/arguments.go +++ b/bot/arguments.go @@ -17,6 +17,12 @@ type Parser interface { Parse(string) error } +// Usager is used in place of the automatically parsed struct name for Parser +// and other interfaces. +type Usager interface { + Usage() string +} + // ManualParser has a ParseContent(string) method. If the library sees // this for an argument, it will send all of the arguments (including the // command) into the method. If used, this should be the only argument followed @@ -145,7 +151,7 @@ func getArgumentValueFn(t reflect.Type) (*Argument, error) { } return &Argument{ - String: t.String(), + String: fromUsager(typeI), Type: typeI, pointer: ptr, fn: avfn, @@ -219,3 +225,16 @@ func quickRet(v interface{}, err error, t reflect.Type) (reflect.Value, error) { return rv.Convert(t), nil } + +func fromUsager(typeI reflect.Type) string { + if typeI.Implements(typeIUsager) { + mt, ok := typeI.MethodByName("Usage") + if !ok { + panic("BUG: type IUsager does not implement Usage") + } + vs := mt.Func.Call([]reflect.Value{reflect.New(typeI.Elem())}) + return vs[0].String() + } + s := strings.Split(typeI.String(), ".") + return s[len(s)-1] +} diff --git a/bot/ctx.go b/bot/ctx.go index e5b5091..60ffa88 100644 --- a/bot/ctx.go +++ b/bot/ctx.go @@ -361,38 +361,27 @@ func (ctx *Context) help(hideAdmin bool) string { help.WriteString("\n---\n") // Generate all commands - help.WriteString("__Commands__\n") - - for _, cmd := range ctx.Commands { - if cmd.Flag.Is(AdminOnly) && hideAdmin { - continue - } - - help.WriteString(indent + cmd.Command) - - switch { - case len(cmd.Usage()) > 0: - help.WriteString(" " + strings.Join(cmd.Usage(), " ")) - case cmd.Description != "": - help.WriteString(": " + cmd.Description) - } - - help.WriteByte('\n') - } + help.WriteString("__Commands__") + help.WriteString(ctx.Subcommand.Help(indent, hideAdmin)) + help.WriteByte('\n') var subHelp = strings.Builder{} var subcommands = ctx.Subcommands() for _, sub := range subcommands { if help := sub.Help(indent, hideAdmin); help != "" { - subHelp.WriteString(help) + for _, line := range strings.Split(help, "\n") { + subHelp.WriteString(indent) + subHelp.WriteString(line) + subHelp.WriteByte('\n') + } } } - if sub := subHelp.String(); sub != "" { + if subHelp.Len() > 0 { help.WriteString("---\n") help.WriteString("__Subcommands__\n") - help.WriteString(sub) + help.WriteString(subHelp.String()) } return help.String() diff --git a/bot/extras/arguments/emoji.go b/bot/extras/arguments/emoji.go index b2183df..58c53ae 100644 --- a/bot/extras/arguments/emoji.go +++ b/bot/extras/arguments/emoji.go @@ -55,6 +55,10 @@ func (e Emoji) URL() string { } } +func (e *Emoji) Usage() string { + return "emoji" +} + func (e *Emoji) Parse(arg string) error { // Check if Unicode emoji if rate.StringIsEmojiOnly(arg) { diff --git a/bot/extras/arguments/link.go b/bot/extras/arguments/link.go new file mode 100644 index 0000000..ce2ad99 --- /dev/null +++ b/bot/extras/arguments/link.go @@ -0,0 +1,62 @@ +package arguments + +import ( + "errors" + "regexp" + + "github.com/diamondburned/arikawa/discord" +) + +// (empty) so it matches standard links +// | OR +// canary. matches canary MessageURL +// 3 `(\d+)` for guild ID, channel ID and message ID +var Regex = regexp.MustCompile( + `https://(|ptb\.|canary\.)discordapp\.com/channels/(\d+)/(\d+)/(\d+)`, +) + +// MessageURL contains info from a MessageURL +type MessageURL struct { + GuildID discord.Snowflake + ChannelID discord.Snowflake + MessageID discord.Snowflake +} + +func (url *MessageURL) Parse(arg string) error { + u := ParseMessageURL(arg) + if u == nil { + return errors.New("Invalid MessageURL format.") + } + *url = *u + return nil +} + +func (url *MessageURL) Usage() string { + return "https\u200b://discordapp.com/channels/\\*/\\*/\\*" +} + +// ParseMessageURL parses the MessageURL into a smartlink +func ParseMessageURL(url string) *MessageURL { + ss := Regex.FindAllStringSubmatch(url, -1) + if ss == nil { + return nil + } + + if len(ss) == 0 || len(ss[0]) != 5 { + return nil + } + + gID, err1 := discord.ParseSnowflake(ss[0][2]) + cID, err2 := discord.ParseSnowflake(ss[0][3]) + mID, err3 := discord.ParseSnowflake(ss[0][4]) + + if err1 != nil || err2 != nil || err3 != nil { + return nil + } + + return &MessageURL{ + GuildID: gID, + ChannelID: cID, + MessageID: mID, + } +} diff --git a/bot/extras/arguments/mention.go b/bot/extras/arguments/mention.go index 09ce153..97d8597 100644 --- a/bot/extras/arguments/mention.go +++ b/bot/extras/arguments/mention.go @@ -18,8 +18,7 @@ var ( type ChannelMention discord.Snowflake func (m *ChannelMention) Parse(arg string) error { - return grabFirst(ChannelRegex, "channel mention", - arg, (*discord.Snowflake)(m)) + return grabFirst(ChannelRegex, "channel mention", arg, (*discord.Snowflake)(m)) } func (m *ChannelMention) Usage() string { @@ -39,8 +38,7 @@ func (m *ChannelMention) Mention() string { type UserMention discord.Snowflake func (m *UserMention) Parse(arg string) error { - return grabFirst(UserRegex, "user mention", - arg, (*discord.Snowflake)(m)) + return grabFirst(UserRegex, "user mention", arg, (*discord.Snowflake)(m)) } func (m *UserMention) Usage() string { @@ -60,8 +58,7 @@ func (m *UserMention) Mention() string { type RoleMention discord.Snowflake func (m *RoleMention) Parse(arg string) error { - return grabFirst(RoleRegex, "role mention", - arg, (*discord.Snowflake)(m)) + return grabFirst(RoleRegex, "role mention", arg, (*discord.Snowflake)(m)) } func (m *RoleMention) Usage() string { @@ -78,9 +75,7 @@ func (m *RoleMention) Mention() string { // -func grabFirst(reg *regexp.Regexp, - item, input string, output *discord.Snowflake) error { - +func grabFirst(reg *regexp.Regexp, item, input string, output *discord.Snowflake) error { matches := reg.FindStringSubmatch(input) if len(matches) < 2 { return errors.New("Invalid " + item) diff --git a/bot/subcommand.go b/bot/subcommand.go index 316412c..6310a1c 100644 --- a/bot/subcommand.go +++ b/bot/subcommand.go @@ -23,6 +23,7 @@ var ( typeIManP = reflect.TypeOf((*ManualParser)(nil)).Elem() typeICusP = reflect.TypeOf((*CustomParser)(nil)).Elem() typeIParser = reflect.TypeOf((*Parser)(nil)).Elem() + typeIUsager = reflect.TypeOf((*Usager)(nil)).Elem() typeSetupFn = func() reflect.Type { method, _ := reflect.TypeOf((*CanSetup)(nil)). Elem(). @@ -182,14 +183,12 @@ func (sub *Subcommand) Help(indent string, hideAdmin bool) string { var header string if sub.Command != "" { - header += indent + sub.Command + header += sub.Command } if sub.Description != "" { if header != "" { header += ": " - } else { - header += indent } header += sub.Description @@ -200,21 +199,27 @@ func (sub *Subcommand) Help(indent string, hideAdmin bool) string { // The commands part: var commands = "" - for _, cmd := range sub.Commands { + for i, cmd := range sub.Commands { if cmd.Flag.Is(AdminOnly) && hideAdmin { continue } - commands += indent + indent + sub.Command + " " + cmd.Command + if sub.Command != "" { + commands += indent + sub.Command + " " + cmd.Command + } else { + commands += indent + cmd.Command + } switch { case len(cmd.Usage()) > 0: - commands += " " + strings.Join(cmd.Usage(), " ") + commands += " **" + strings.Join(cmd.Usage(), " ") + "**" case cmd.Description != "": commands += ": " + cmd.Description } - commands += "\n" + if i != len(sub.Commands)-1 { + commands += "\n" + } } if commands == "" { diff --git a/state/state.go b/state/state.go index c925a8d..e00af66 100644 --- a/state/state.go +++ b/state/state.go @@ -148,9 +148,7 @@ func (s *State) MemberColor(guildID, userID discord.Snowflake) discord.Color { //// -func (s *State) Permissions( - channelID, userID discord.Snowflake) (discord.Permissions, error) { - +func (s *State) Permissions(channelID, userID discord.Snowflake) (discord.Permissions, error) { ch, err := s.Channel(channelID) if err != nil { return 0, errors.Wrap(err, "Failed to get channel")