3 changed files with 311 additions and 0 deletions
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
module git.hugfreevikings.wtf/bots/macro |
||||
|
||||
go 1.15 |
||||
|
||||
require ( |
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 |
||||
github.com/kf5grd/keybasebot v1.7.0 |
||||
github.com/urfave/cli/v2 v2.3.0 |
||||
samhofi.us/x/keybase/v2 v2.1.1 |
||||
) |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= |
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= |
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= |
||||
github.com/kf5grd/keybasebot v1.7.0 h1:8Jl6RSYEIyzniZ7UFyH/fjTlsM0tZHTCZ5F8bq8JTDA= |
||||
github.com/kf5grd/keybasebot v1.7.0/go.mod h1:8T07cWZZrl2G6hTRsL9x2SBwaH8gEZocF9NRknSU3dY= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= |
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= |
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= |
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
samhofi.us/x/keybase/v2 v2.0.8/go.mod h1:lJivwhzMSV+WUg+XUbatszStjjFVcuLGl+xcQpqQ5GQ= |
||||
samhofi.us/x/keybase/v2 v2.1.1 h1:XPWrmdbJCrNcsW3sRuR6WuALYOZt7O+av0My6YoehqE= |
||||
samhofi.us/x/keybase/v2 v2.1.1/go.mod h1:lJivwhzMSV+WUg+XUbatszStjjFVcuLGl+xcQpqQ5GQ= |
@ -0,0 +1,281 @@
@@ -0,0 +1,281 @@
|
||||
// This is a very simple bot that has 2 commands: set, and get. The set command sets a
|
||||
// string variable named "message" in the Meta store, and the get command retrieves that
|
||||
// variable and sends it to the user in a chat message.
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"strings" |
||||
|
||||
"github.com/google/shlex" |
||||
"github.com/urfave/cli/v2" |
||||
|
||||
bot "github.com/kf5grd/keybasebot" |
||||
"github.com/kf5grd/keybasebot/pkg/util" |
||||
"samhofi.us/x/keybase/v2" |
||||
"samhofi.us/x/keybase/v2/types/chat1" |
||||
) |
||||
|
||||
const ( |
||||
back = "`" |
||||
backs = "```" |
||||
) |
||||
|
||||
type Command struct { |
||||
Name string |
||||
Args []string |
||||
Response string |
||||
} |
||||
|
||||
// Current version
|
||||
var version string |
||||
|
||||
// Exit code on failure
|
||||
const exitFail = 1 |
||||
|
||||
func main() { |
||||
app := cli.App{ |
||||
Name: "funcy", |
||||
Usage: "Go interpreter for Keybase", |
||||
Version: version, |
||||
Writer: os.Stdout, |
||||
EnableBashCompletion: true, |
||||
Action: run, |
||||
Flags: []cli.Flag{ |
||||
&cli.PathFlag{ |
||||
Name: "home", |
||||
Aliases: []string{"H"}, |
||||
Usage: "Keybase Home Folder", |
||||
EnvVars: []string{"MACROBOT_HOME"}, |
||||
}, |
||||
&cli.StringFlag{ |
||||
Name: "bot-owner", |
||||
Usage: "Username of the bot owner", |
||||
EnvVars: []string{"MACROBOT_OWNER"}, |
||||
}, |
||||
&cli.StringFlag{ |
||||
Name: "log-conv", |
||||
Usage: "Conversation ID to send logs to", |
||||
EnvVars: []string{"MACROBOT_LOGCONV"}, |
||||
}, |
||||
&cli.BoolFlag{ |
||||
Name: "debug", |
||||
Aliases: []string{"d"}, |
||||
Usage: "Enable extra log output", |
||||
EnvVars: []string{"MACROBOT_DEBUG"}, |
||||
}, |
||||
&cli.BoolFlag{ |
||||
Name: "json", |
||||
Aliases: []string{"j"}, |
||||
Usage: "Output log in JSON format", |
||||
EnvVars: []string{"MACROBOT_JSON"}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
if err := app.Run(os.Args); err != nil { |
||||
os.Exit(exitFail) |
||||
} |
||||
} |
||||
|
||||
func run(c *cli.Context) error { |
||||
// setup bot
|
||||
b := bot.New("", keybase.SetHomePath(c.Path("home"))) |
||||
b.Debug = c.Bool("debug") |
||||
b.JSON = c.Bool("json") |
||||
b.LogConv = chat1.ConvIDStr(c.String("log-conv")) |
||||
b.LogWriter = os.Stdout |
||||
|
||||
// register the bot's commands
|
||||
b.Commands = append(b.Commands, |
||||
bot.BotCommand{ |
||||
Name: "CreateCommand", |
||||
Ad: adCreateCommand(), |
||||
Run: bot.Adapt(cmdCreateCommand, |
||||
bot.MessageType("text"), |
||||
bot.CommandPrefix("!create"), |
||||
), |
||||
}, |
||||
) |
||||
|
||||
// run bot
|
||||
b.Run() |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func adCreateCommand() *chat1.UserBotCommandInput { |
||||
var createDesc = fmt.Sprintf(`Commands should be in the following format, and should be followed by the response: |
||||
%s |
||||
<command>(<arg name 1>, <arg name 2>, ... <arg name n>) |
||||
%s |
||||
|
||||
You can reference the arguments in the response by prepending the argument name with a %s$%s |
||||
|
||||
Here's an example command that can be used to tell the bot to say "Hello" to a specific person: |
||||
%s |
||||
!create sayhi(user) Hello, @$user! |
||||
%s |
||||
`, backs, backs, back, back, backs, backs) |
||||
|
||||
return &chat1.UserBotCommandInput{ |
||||
Name: "create", |
||||
Usage: "<command> <response>", |
||||
Description: "Create a new command", |
||||
ExtendedDescription: &chat1.UserBotExtendedDescription{ |
||||
Title: "Create A New Command", |
||||
DesktopBody: createDesc, |
||||
MobileBody: createDesc, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func cmdCreateCommand(m chat1.MsgSummary, b *bot.Bot) (bool, error) { |
||||
body := m.Content.Text.Body |
||||
fields := strings.Fields(body) |
||||
if len(fields) < 3 { |
||||
return true, fmt.Errorf("Must provide both command and response") |
||||
} |
||||
|
||||
command, err := parseCommandDef(strings.Replace(body, "!create", "", 1)) |
||||
if err != nil { |
||||
return true, err |
||||
} |
||||
|
||||
metaIndex := fmt.Sprintf("%s-%s", m.ConvID, command.Name) |
||||
if _, ok := b.Meta[metaIndex]; ok { |
||||
return true, fmt.Errorf("That command already exists") |
||||
} |
||||
|
||||
responsePrefix := strings.Fields(command.Response)[0] |
||||
if strings.HasPrefix("/", responsePrefix) && !util.StringInSlice(responsePrefix, []string{"/giphy", "/flip", "/me", "/shrug"}) { |
||||
return true, fmt.Errorf("The only `/` commands allowed are `/giphy`, `/flip`, `/me`, and `/shrug`. Nice try though!") |
||||
} |
||||
|
||||
for _, c := range b.Commands { |
||||
if command.Name == strings.ToLower(c.Ad.Name) && c.AdType != "conv" { |
||||
return true, fmt.Errorf("You cannot override an existing public command") |
||||
} |
||||
} |
||||
|
||||
var usageString string |
||||
if len(command.Args) > 0 { |
||||
for _, arg := range command.Args { |
||||
usageString = fmt.Sprintf("%s <%s>", usageString, arg) |
||||
} |
||||
usageString = strings.TrimSpace(usageString) |
||||
} |
||||
|
||||
b.Meta[metaIndex] = command.Response |
||||
b.Commands = append(b.Commands, bot.BotCommand{ |
||||
Name: fmt.Sprintf("Custom [%s]", metaIndex), |
||||
AdType: "conv", |
||||
AdConv: m.ConvID, |
||||
Ad: &chat1.UserBotCommandInput{ |
||||
Name: command.Name, |
||||
Usage: usageString, |
||||
Description: command.Response, |
||||
}, |
||||
Run: bot.Adapt(cmdCommand(command), |
||||
bot.MessageType("text"), |
||||
bot.CommandPrefix("!"+command.Name), |
||||
), |
||||
}) |
||||
b.AdvertiseCommands() |
||||
|
||||
b.KB.ReactByConvID(m.ConvID, m.Id, ":heavy_check_mark:") |
||||
return true, nil |
||||
} |
||||
|
||||
func cmdCommand(cmd Command) bot.BotAction { |
||||
return func(m chat1.MsgSummary, b *bot.Bot) (bool, error) { |
||||
argText := strings.Replace(m.Content.Text.Body, "!", "", 1) |
||||
argText = strings.TrimSpace(argText) |
||||
|
||||
a, _ := shlex.Split(argText) |
||||
var receivedArgs []string |
||||
command := a[0] |
||||
if len(a) > 1 { |
||||
receivedArgs = a[1:] |
||||
} |
||||
|
||||
if cmd.Name != command { |
||||
return false, nil |
||||
} |
||||
|
||||
metaIndex := fmt.Sprintf("%s-%s", m.ConvID, cmd.Name) |
||||
response, ok := b.Meta[metaIndex] |
||||
if !ok { |
||||
return false, nil |
||||
} |
||||
|
||||
if len(receivedArgs) != len(cmd.Args) { |
||||
return true, fmt.Errorf("Incorrect number of arguments supplied") |
||||
} |
||||
|
||||
b.Logger.Debug("cmd.Name: %s, cmd.Args: %s, argText: %s, receivedArgs: %s", cmd.Name, cmd.Args, argText, receivedArgs) |
||||
|
||||
cleanResponse := response.(string) |
||||
cleanResponse = strings.ReplaceAll(cleanResponse, "%", "%%") |
||||
cleanResponse = strings.ReplaceAll(cleanResponse, "$USER", m.Sender.Username) |
||||
|
||||
for argIndex, arg := range cmd.Args { |
||||
cleanResponse = strings.ReplaceAll(cleanResponse, "$"+strings.TrimSpace(arg), receivedArgs[argIndex]) |
||||
} |
||||
|
||||
b.KB.SendMessageByConvID(m.ConvID, cleanResponse) |
||||
return true, nil |
||||
} |
||||
} |
||||
|
||||
func parseCommandDef(def string) (Command, error) { |
||||
// def: funcName(arg1, arg2) response goes here
|
||||
def = strings.TrimSpace(def) |
||||
|
||||
argStart := strings.Index(def, "(") + 1 |
||||
argEnd := strings.Index(def, ")") |
||||
|
||||
if argStart == -1 || argEnd == -1 { |
||||
return Command{}, fmt.Errorf("Invalid command definition") |
||||
} |
||||
|
||||
name := strings.ToLower(strings.TrimSpace(def[0 : argStart-1])) |
||||
|
||||
args := strings.Split(def[argStart:argEnd], ",") |
||||
args = trimArgs(args) |
||||
|
||||
response := strings.TrimSpace(def[argEnd+1:]) |
||||
|
||||
if len(args) > 0 { |
||||
missing := make([]string, 0) |
||||
for _, arg := range args { |
||||
if !strings.Contains(response, "$"+arg) { |
||||
missing = append(missing, arg) |
||||
} |
||||
} |
||||
if len(missing) > 0 { |
||||
return Command{}, fmt.Errorf("The following variable(s) were set in the command, but not referenced in the response: %s", strings.Join(missing, ", ")) |
||||
} |
||||
} |
||||
|
||||
ret := Command{ |
||||
Name: name, |
||||
Args: args, |
||||
Response: response, |
||||
} |
||||
|
||||
return ret, nil |
||||
} |
||||
|
||||
func trimArgs(args []string) []string { |
||||
newArgs := make([]string, 0) |
||||
for _, arg := range args { |
||||
trimmed := strings.TrimSpace(arg) |
||||
if trimmed != "" { |
||||
newArgs = append(newArgs, trimmed) |
||||
} |
||||
} |
||||
|
||||
return newArgs |
||||
} |
Loading…
Reference in new issue