Add bot code
This commit is contained in:
10
go.mod
Normal file
10
go.mod
Normal file
@ -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
|
||||
)
|
||||
20
go.sum
Normal file
20
go.sum
Normal file
@ -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=
|
||||
281
main.go
Normal file
281
main.go
Normal file
@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user