From 79dedcde58b3517911a1a34f2d6908e4fefd5fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20J=C3=B6hren?= Date: Mon, 28 Aug 2017 14:34:14 +0200 Subject: [PATCH 01/42] Adds message filter to tweak slack params in post message (#61) --- slack/slack.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/slack/slack.go b/slack/slack.go index 9368791..3cc3229 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -8,17 +8,25 @@ import ( "github.com/nlopes/slack" ) +type MessageFilter func(string, *bot.User) (string, slack.PostMessageParameters) + var ( rtm *slack.RTM api *slack.Client teaminfo *slack.TeamInfo - channelList = map[string]slack.Channel{} - params = slack.PostMessageParameters{AsUser: true} - botUserID = "" + channelList = map[string]slack.Channel{} + params = slack.PostMessageParameters{AsUser: true} + messageFilter MessageFilter = defaultMessageFilter + botUserID = "" ) +func defaultMessageFilter(message string, sender *bot.User) (string, slack.PostMessageParameters) { + return message, params +} + func responseHandler(target string, message string, sender *bot.User) { + message, params := messageFilter(message, sender) api.PostMessage(target, message, params) } @@ -87,6 +95,14 @@ func ownMessage(UserID string) bool { return botUserID == UserID } +func RunWithFilter(token string, customMessageFilter MessageFilter) { + if customMessageFilter == nil { + panic("A valid message filter must be provided.") + } + messageFilter = customMessageFilter + Run(token) +} + // Run connects to slack RTM API using the provided token func Run(token string) { api = slack.New(token) @@ -96,6 +112,7 @@ func Run(token string) { b := bot.New(&bot.Handlers{ Response: responseHandler, }) + b.Disable([]string{"url"}) go rtm.ManageConnection() From b4a369ff3871115fd2c84ff003a23355819b50cf Mon Sep 17 00:00:00 2001 From: Max Roeleveld Date: Sat, 2 Sep 2017 19:03:24 +0200 Subject: [PATCH 02/42] Small bug fixes (#62) * Fix possible log bug - also rocket messages aren't actions by definition. --- rocket/rocket.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rocket/rocket.go b/rocket/rocket.go index 33edad6..ea9f5ec 100644 --- a/rocket/rocket.go +++ b/rocket/rocket.go @@ -2,10 +2,11 @@ package rocket import ( "fmt" + "log" + "github.com/go-chat-bot/bot" "github.com/pyinx/gorocket/api" "github.com/pyinx/gorocket/rest" - "log" ) var ( @@ -49,7 +50,7 @@ func Run(c *Config) { client = rest.NewClient(config.Server, config.Port, config.UseTLS, config.Debug) err := client.Login(api.UserCredentials{Email: config.Email, Name: config.User, Password: config.Password}) if err != nil { - log.Fatal("login err: %s\n", err) + log.Fatalf("login err: %s\n", err) } b := bot.New(&bot.Handlers{ @@ -70,7 +71,7 @@ func Run(c *Config) { Channel: msg.ChannelId, IsPrivate: false, }, - &bot.Message{Text: msg.Text, IsAction: true}, + &bot.Message{Text: msg.Text}, &bot.User{ID: msg.User.Id, RealName: msg.User.UserName, Nick: msg.User.UserName, IsBot: false}) } From 4cfbc406268969e337bef54902f7df68aa315144 Mon Sep 17 00:00:00 2001 From: Mike Baynton Date: Wed, 3 Jan 2018 10:04:31 -0600 Subject: [PATCH 03/42] Make it possible to use the bot to send notifications (#65) Modifications to make it possible to use the bot in a larger program that sends notifications in response to non-bot events. --- bot.go | 9 +++++++-- cmd.go | 10 +++++----- help.go | 8 ++++---- irc/irc.go | 19 +++++++++++++++---- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/bot.go b/bot.go index c33cd72..a300b10 100644 --- a/bot.go +++ b/bot.go @@ -49,7 +49,7 @@ func (b *Bot) startPeriodicCommands() { if err != nil { log.Print("Periodic command failed ", err) } else if message != "" { - b.handlers.Response(channel, message, nil) + b.SendMessage(channel, message, nil) } } }) @@ -64,7 +64,7 @@ func (b *Bot) startPeriodicCommands() { func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *User) { command, err := parse(message.Text, channel, sender) if err != nil { - b.handlers.Response(channel.Channel, err.Error(), sender) + b.SendMessage(channel.Channel, err.Error(), sender) return } @@ -91,6 +91,11 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us } } +// Sends a message to a target recipient, optionally from a particular sender. +func (b *Bot) SendMessage(target string, message string, sender *User) { + b.handlers.Response(target, message, sender) +} + func init() { rand.Seed(time.Now().UnixNano()) } diff --git a/cmd.go b/cmd.go index 6f4f1ec..c3eebda 100644 --- a/cmd.go +++ b/cmd.go @@ -192,7 +192,7 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { log.Println(err) } else { mutex.Lock() - b.handlers.Response(cmd.Channel, result, cmd.User) + b.SendMessage(cmd.Channel, result, cmd.User) mutex.Unlock() } }() @@ -222,7 +222,7 @@ func (b *Bot) handleCmd(c *Cmd) { message, err := cmd.CmdFuncV1(c) b.checkCmdError(err, c) if message != "" { - b.handlers.Response(c.Channel, message, c.User) + b.SendMessage(c.Channel, message, c.User) } case v2: result, err := cmd.CmdFuncV2(c) @@ -232,7 +232,7 @@ func (b *Bot) handleCmd(c *Cmd) { } if result.Message != "" { - b.handlers.Response(result.Channel, result.Message, c.User) + b.SendMessage(result.Channel, result.Message, c.User) } case v3: result, err := cmd.CmdFuncV3(c) @@ -244,7 +244,7 @@ func (b *Bot) handleCmd(c *Cmd) { select { case message := <-result.Message: if message != "" { - b.handlers.Response(result.Channel, message, c.User) + b.SendMessage(result.Channel, message, c.User) } case <-result.Done: return @@ -257,6 +257,6 @@ func (b *Bot) checkCmdError(err error, c *Cmd) { if err != nil { errorMsg := fmt.Sprintf(errorExecutingCommand, c.Command, err.Error()) log.Printf(errorMsg) - b.handlers.Response(c.Channel, errorMsg, c.User) + b.SendMessage(c.Channel, errorMsg, c.User) } } diff --git a/help.go b/help.go index a02c2f6..3ea8cb3 100644 --- a/help.go +++ b/help.go @@ -31,9 +31,9 @@ func (b *Bot) help(c *Cmd) { func (b *Bot) showHelp(c *Cmd, help *customCommand) { if help.Description != "" { - b.handlers.Response(c.Channel, fmt.Sprintf(helpDescripton, help.Description), c.User) + b.SendMessage(c.Channel, fmt.Sprintf(helpDescripton, help.Description), c.User) } - b.handlers.Response(c.Channel, fmt.Sprintf(helpUsage, CmdPrefix, c.Command, help.ExampleArgs), c.User) + b.SendMessage(c.Channel, fmt.Sprintf(helpUsage, CmdPrefix, c.Command, help.ExampleArgs), c.User) } func (b *Bot) showAvailabeCommands(channel string, sender *User) { @@ -41,6 +41,6 @@ func (b *Bot) showAvailabeCommands(channel string, sender *User) { for k := range commands { cmds = append(cmds, k) } - b.handlers.Response(channel, fmt.Sprintf(helpAboutCommand, CmdPrefix), sender) - b.handlers.Response(channel, fmt.Sprintf(availableCommands, strings.Join(cmds, ", ")), sender) + b.SendMessage(channel, fmt.Sprintf(helpAboutCommand, CmdPrefix), sender) + b.SendMessage(channel, fmt.Sprintf(availableCommands, strings.Join(cmds, ", ")), sender) } diff --git a/irc/irc.go b/irc/irc.go index 0fb0075..9fa7f3f 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -90,9 +90,10 @@ func onWelcome(e *ircevent.Event) { } } -// Run reads the Config, connect to the specified IRC server and starts the bot. -// The bot will automatically join all the channels specified in the configuration -func Run(c *Config) { +// SetUp returns a bot for irc according to the Config, but does not run it. +// When you are ready to run the bot, call Run(nil). +// This is useful if you need a pointer to the bot, otherwise you can simply call Run(). +func SetUp(c *Config) *bot.Bot { config = c ircConn = ircevent.IRC(c.User, c.Nick) @@ -111,7 +112,17 @@ func Run(c *Config) { ircConn.AddCallback("PRIVMSG", onPRIVMSG) ircConn.AddCallback("CTCP_ACTION", onCTCPACTION) - err := ircConn.Connect(c.Server) + return b +} + +// Run reads the Config, connect to the specified IRC server and starts the bot. +// The bot will automatically join all the channels specified in the configuration +func Run(c *Config) { + if c != nil { + SetUp(c) + } + + err := ircConn.Connect(config.Server) if err != nil { log.Fatal(err) } From a9ecb615598db4d33483312630e5831870f42518 Mon Sep 17 00:00:00 2001 From: Balazs Nadasdi Date: Fri, 9 Feb 2018 15:36:01 +0100 Subject: [PATCH 04/42] Enable SlackUser lookup from commands (#66) --- slack/slack.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/slack/slack.go b/slack/slack.go index 3cc3229..2ea8655 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -30,6 +30,22 @@ func responseHandler(target string, message string, sender *bot.User) { api.PostMessage(target, message, params) } +// FindUserBySlackID converts a slack.User into a bot.User struct +func FindUserBySlackID(userID string) *bot.User { + slackUser, err := api.GetUserInfo(userID) + if err != nil { + fmt.Printf("Error retrieving slack user: %s\n", err) + return &bot.User{ + ID: userID, + IsBot: false} + } + return &bot.User{ + ID: userID, + Nick: slackUser.Name, + RealName: slackUser.Profile.RealName, + IsBot: slackUser.IsBot} +} + // Extracts user information from slack API func extractUser(event *slack.MessageEvent) *bot.User { var isBot bool @@ -41,18 +57,12 @@ func extractUser(event *slack.MessageEvent) *bot.User { userID = event.User isBot = false } - slackUser, err := api.GetUserInfo(userID) - if err != nil { - fmt.Printf("Error retrieving slack user: %s\n", err) - return &bot.User{ - ID: userID, - IsBot: isBot} + user := FindUserBySlackID(userID) + if len(user.Nick) == 0 { + user.IsBot = isBot } - return &bot.User{ - ID: userID, - Nick: slackUser.Name, - RealName: slackUser.Profile.RealName, - IsBot: isBot} + + return user } func extractText(event *slack.MessageEvent) *bot.Message { From d08a76613329826b7be4ac0bae3daf912c92b13e Mon Sep 17 00:00:00 2001 From: teddy Date: Sat, 3 Mar 2018 02:29:09 -0800 Subject: [PATCH 05/42] Update Design For Concurrency! (#70) * Execute Slack commands asynchronously It is pretty annoying that a long running command hangs the whole bot. Now commands will be executed on a launched goroutine! Yay! All objects passed into MessageReceived are generated at the call site, so there can be no concurrency issues there. We can later look into making the others concurrent, but for now I just want Slack to work this way. * Create PassiveCommandV2 This will be a passive command that can return multiple messages. * Restructure concurrency for message sending This should make it much easier to have many go routines sending messages back to the chat service at the same time, but streamlining them through a channel. I had to add a way to skip the async sending in tests because otherwise I would likely end up having to add sleeps all over the place. --- .gitignore | 6 ++++ bot.go | 56 +++++++++++++++++++++++++++++++--- cmd.go | 83 +++++++++++++++++++++++++++++++++++++------------- cmd_test.go | 62 ++++++++++++++++++++++++++++--------- slack/slack.go | 7 +++-- 5 files changed, 171 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index f980622..7e8b8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ go-bot *.o *.a *.so +pkg/ # Folders _obj @@ -33,3 +34,8 @@ _testmain.go # test coverage report files *.out *.html + +# go deps storage +src/github.com/* +src/gopkg.in/* + diff --git a/bot.go b/bot.go index a300b10..7115fe2 100644 --- a/bot.go +++ b/bot.go @@ -13,6 +13,10 @@ const ( // CmdPrefix is the prefix used to identify a command. // !hello would be identified as a command CmdPrefix = "!" + + // MsgBuffer is the max number of messages which can be buffered + // while waiting to flush them to the chat service. + MsgBuffer = 10 ) // Bot handles the bot instance @@ -20,6 +24,16 @@ type Bot struct { handlers *Handlers cron *cron.Cron disabledCmds []string + + synchronousMessageSending bool + + msgsToSend chan *responseMessage + done chan struct{} +} + +type responseMessage struct { + target, message string + sender *User } // ResponseHandler must be implemented by the protocol to handle the bot responses @@ -33,9 +47,15 @@ type Handlers struct { // New configures a new bot instance func New(h *Handlers) *Bot { b := &Bot{ - handlers: h, - cron: cron.New(), + handlers: h, + cron: cron.New(), + msgsToSend: make(chan *responseMessage, MsgBuffer), + done: make(chan struct{}), } + // Launch the background goroutine that isolates the possibly non-threadsafe + // message sending logic of the underlying transport layer. + go b.messageSender() + b.startPeriodicCommands() return b } @@ -91,9 +111,37 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us } } -// Sends a message to a target recipient, optionally from a particular sender. +// SendMessage queues a message for a target recipient, optionally from a particular sender. func (b *Bot) SendMessage(target string, message string, sender *User) { - b.handlers.Response(target, message, sender) + if b.synchronousMessageSending { + b.handlers.Response(target, message, sender) + return + } + + select { + case b.msgsToSend <- &responseMessage{ + target, message, sender, + }: + default: + log.Printf("Failed to queue message to send. Must be busy.") + } +} + +func (b *Bot) messageSender() { + for { + select { + case msg := <-b.msgsToSend: + b.handlers.Response(msg.target, msg.message, msg.sender) + case <-b.done: + return + } + } +} + +// Close will shut down the message sending capabilities of this bot. Call +// this when you are done using the bot. +func (b *Bot) Close() { + close(b.done) } func init() { diff --git a/cmd.go b/cmd.go index c3eebda..39b383b 100644 --- a/cmd.go +++ b/cmd.go @@ -63,13 +63,15 @@ type User struct { } type customCommand struct { - Version int - Cmd string - CmdFuncV1 activeCmdFuncV1 - CmdFuncV2 activeCmdFuncV2 - CmdFuncV3 activeCmdFuncV3 - Description string - ExampleArgs string + Version int + Cmd string + CmdFuncV1 activeCmdFuncV1 + CmdFuncV2 activeCmdFuncV2 + CmdFuncV3 activeCmdFuncV3 + PassiveFuncV1 passiveCmdFuncV1 + PassiveFuncV2 passiveCmdFuncV2 + Description string + ExampleArgs string } // CmdResult is the result message of V2 commands @@ -89,6 +91,8 @@ const ( v1 = iota v2 v3 + pv1 + pv2 ) const ( @@ -97,14 +101,16 @@ const ( errorExecutingCommand = "Error executing %s: %s" ) -type passiveCmdFunc func(cmd *PassiveCmd) (string, error) +type passiveCmdFuncV1 func(cmd *PassiveCmd) (string, error) +type passiveCmdFuncV2 func(cmd *PassiveCmd) (CmdResultV3, error) + type activeCmdFuncV1 func(cmd *Cmd) (string, error) type activeCmdFuncV2 func(cmd *Cmd) (CmdResult, error) type activeCmdFuncV3 func(cmd *Cmd) (CmdResultV3, error) var ( commands = make(map[string]*customCommand) - passiveCommands = make(map[string]passiveCmdFunc) + passiveCommands = make(map[string]*customCommand) periodicCommands = make(map[string]PeriodicConfig) ) @@ -153,8 +159,25 @@ func RegisterCommandV3(command, description, exampleArgs string, cmdFunc activeC // Passive commands receives all the text posted to a channel without any parsing // command: String used to identify the command, for internal use only (ex: logs) // cmdFunc: Function which will be executed. It will received the raw message, channel and nick -func RegisterPassiveCommand(command string, cmdFunc func(cmd *PassiveCmd) (string, error)) { - passiveCommands[command] = cmdFunc +func RegisterPassiveCommand(command string, cmdFunc passiveCmdFuncV1) { + passiveCommands[command] = &customCommand{ + Version: pv1, + Cmd: command, + PassiveFuncV1: cmdFunc, + } +} + +// RegisterPassiveCommandV2 adds a new passive command to the bot. +// The command should be registered in the Init() func of your package +// Passive commands receives all the text posted to a channel without any parsing +// command: String used to identify the command, for internal use only (ex: logs) +// cmdFunc: Function which will be executed. It will received the raw message, channel and nick +func RegisterPassiveCommandV2(command string, cmdFunc passiveCmdFuncV2) { + passiveCommands[command] = &customCommand{ + Version: pv2, + Cmd: command, + PassiveFuncV2: cmdFunc, + } } // RegisterPeriodicCommand adds a command that is run periodically. @@ -174,28 +197,44 @@ func (b *Bot) Disable(cmds []string) { func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { var wg sync.WaitGroup - mutex := &sync.Mutex{} for k, v := range passiveCommands { if b.isDisabled(k) { continue } - cmdFunc := v wg.Add(1) - go func() { + go func(cmdFunc *customCommand) { defer wg.Done() - result, err := cmdFunc(cmd) - if err != nil { - log.Println(err) - } else { - mutex.Lock() - b.SendMessage(cmd.Channel, result, cmd.User) - mutex.Unlock() + switch cmdFunc.Version { + case pv1: + result, err := cmdFunc.PassiveFuncV1(cmd) + if err != nil { + log.Println(err) + } else { + b.SendMessage(cmd.Channel, result, cmd.User) + } + case pv2: + result, err := cmdFunc.PassiveFuncV2(cmd) + if err != nil { + log.Println(err) + return + } + for { + select { + case message := <-result.Message: + if message != "" { + b.SendMessage(result.Channel, message, cmd.User) + } + case <-result.Done: + return + } + } + default: } - }() + }(v) } wg.Wait() } diff --git a/cmd_test.go b/cmd_test.go index f348e2a..5371d25 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -62,6 +62,8 @@ func TestPeriodicCommands(t *testing.T) { CmdFunc: func(channel string) (string, error) { return "ok " + channel, nil }, }) b := New(&Handlers{Response: responseHandler}) + b.synchronousMessageSending = true + defer b.Close() entries := b.cron.Entries() if len(entries) != 1 { @@ -97,6 +99,8 @@ func TestMultiplePeriodicCommands(t *testing.T) { CmdFunc: func(channel string) (string, error) { return "ok_afternoon " + channel, nil }, }) b := New(&Handlers{Response: responseHandler}) + b.synchronousMessageSending = true + defer b.Close() entries := b.cron.Entries() if len(entries) != 2 { @@ -133,6 +137,8 @@ func TestErroredPeriodicCommand(t *testing.T) { CmdFunc: func(channel string) (string, error) { return "bug", errors.New("error") }, }) b := New(&Handlers{Response: responseHandler}) + b.synchronousMessageSending = true + defer b.Close() entries := b.cron.Entries() @@ -153,6 +159,7 @@ func TestDisabledCommands(t *testing.T) { b := New(&Handlers{ Response: responseHandler, }) + b.synchronousMessageSending = true RegisterCommand("cmd", "", "", func(c *Cmd) (string, error) { @@ -181,7 +188,9 @@ func TestDisabledCommands(t *testing.T) { func TestCommandNotRegistered(t *testing.T) { resetResponses() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!not_a_cmd"}, &User{}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!not_a_cmd"}, &User{}) if len(replies) != 0 { t.Fatal("Should not reply if a command is not found") @@ -192,7 +201,9 @@ func TestInvalidCmdArgs(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd \"invalid arg"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd \"invalid arg"}, &User{Nick: "user"}) if channel != "#go-bot" { t.Error("Should reply to #go-bot channel") @@ -213,7 +224,9 @@ func TestErroredCmd(t *testing.T) { return "", cmdError }) - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) if channel != "#go-bot" { t.Fatal("Invalid channel") @@ -230,7 +243,9 @@ func TestValidCmdOnChannel(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) if channel != "#go-bot" { t.Fatal("Command called on channel should reply to channel") @@ -257,7 +272,9 @@ func TestChannelData(t *testing.T) { func TestHelpWithNoArgs(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help"}, &User{Nick: "user"}) expectedReply := []string{ fmt.Sprintf(helpAboutCommand, CmdPrefix), @@ -273,6 +290,7 @@ func TestDisableHelp(t *testing.T) { resetResponses() registerValidCommand() b := newBot() + b.synchronousMessageSending = true b.Disable([]string{"help"}) b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help"}, &User{Nick: "user"}) @@ -284,7 +302,9 @@ func TestDisableHelp(t *testing.T) { func TestHelpForACommand(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd"}, &User{Nick: "user"}) expectedReply := []string{ fmt.Sprintf(helpDescripton, cmdDescription), @@ -299,7 +319,9 @@ func TestHelpForACommand(t *testing.T) { func TestHelpWithNonExistingCommand(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help not_a_cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help not_a_cmd"}, &User{Nick: "user"}) expectedReply := []string{ fmt.Sprintf(helpAboutCommand, CmdPrefix), @@ -314,7 +336,9 @@ func TestHelpWithNonExistingCommand(t *testing.T) { func TestHelpWithInvalidArgs(t *testing.T) { resetResponses() registerValidCommand() - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd \"invalid arg"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd \"invalid arg"}, &User{Nick: "user"}) if len(replies) != 1 { t.Fatal("Invalid reply") @@ -333,7 +357,9 @@ func TestCmdV2(t *testing.T) { Message: "message"}, nil }) - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) if channel != "#channel" { t.Error("Wrong channel") @@ -350,7 +376,9 @@ func TestCmdV2WithoutSpecifyingChannel(t *testing.T) { return CmdResult{Message: "message"}, nil }) - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) if channel != "#go-bot" { t.Error("Should reply to original channel if no channel is returned") @@ -360,7 +388,7 @@ func TestCmdV2WithoutSpecifyingChannel(t *testing.T) { func TestPassiveCommand(t *testing.T) { resetResponses() - passiveCommands = make(map[string]passiveCmdFunc) + passiveCommands = make(map[string]*customCommand) echo := func(cmd *PassiveCmd) (string, error) { return cmd.Raw, nil @@ -376,7 +404,9 @@ func TestPassiveCommand(t *testing.T) { RegisterPassiveCommand("ping", ping) RegisterPassiveCommand("errored", errored) - newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) if channel != "#go-bot" { t.Error("Invalid channel") @@ -405,7 +435,9 @@ func TestCmdV3(t *testing.T) { return result, nil }) - go newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + go b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) result.Message <- "message" result.Done <- true @@ -427,7 +459,9 @@ func TestCmdV3WithoutSpecifyingChannel(t *testing.T) { return result, nil }) - go newBot().MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + b := newBot() + b.synchronousMessageSending = true + go b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) result.Message <- "message" result.Done <- true diff --git a/slack/slack.go b/slack/slack.go index 2ea8655..d01e3fd 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -21,7 +21,7 @@ var ( botUserID = "" ) -func defaultMessageFilter(message string, sender *bot.User) (string, slack.PostMessageParameters) { +func defaultMessageFilter(message string, _ *bot.User) (string, slack.PostMessageParameters) { return message, params } @@ -147,7 +147,7 @@ Loop: if C.IsChannel { channel = fmt.Sprintf("#%s", C.Name) } - b.MessageReceived( + go b.MessageReceived( &bot.ChannelData{ Protocol: "slack", Server: teaminfo.Domain, @@ -155,7 +155,8 @@ Loop: IsPrivate: !C.IsChannel, }, extractText(ev), - extractUser(ev)) + extractUser(ev), + ) } case *slack.RTMError: From ddd667288ed8b9c5765e09928b271f68881d13b9 Mon Sep 17 00:00:00 2001 From: teddy Date: Sat, 3 Mar 2018 02:31:20 -0800 Subject: [PATCH 06/42] Pass the channel name to the bot channel data (#67) This makes it much easier to act on which channel the message is coming from. --- cmd.go | 1 + slack/slack.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd.go b/cmd.go index 39b383b..5db684e 100644 --- a/cmd.go +++ b/cmd.go @@ -24,6 +24,7 @@ type ChannelData struct { Protocol string // What protocol the message was sent on (irc, slack, telegram) Server string // The server hostname the message was sent on Channel string // The channel name the message appeared in + HumanName string // The human readable name of the channel. IsPrivate bool // Whether the channel is a group or private chat } diff --git a/slack/slack.go b/slack/slack.go index d01e3fd..daa3521 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -152,6 +152,7 @@ Loop: Protocol: "slack", Server: teaminfo.Domain, Channel: channel, + HumanName: C.Name, IsPrivate: !C.IsChannel, }, extractText(ev), From 3cda8d6c8953a59c4a95f5be810755805d36e7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sun, 18 Mar 2018 09:59:23 -0300 Subject: [PATCH 07/42] Fix race condition (#71) * Fix race conditions on tests and some refactorings - Added errorHandler to handle errors - Removed all the console logs - Remove the synchronous command option - Refactored the tests to wait for the replies using a channel * Adds PassiveCommandV2 test * Fix error handling on tests --- bot.go | 54 +++++---- cmd.go | 10 +- cmd_test.go | 277 ++++++++++++++++++++++++++----------------- slack/slack.go | 4 + telegram/telegram.go | 3 +- 5 files changed, 212 insertions(+), 136 deletions(-) diff --git a/bot.go b/bot.go index 7115fe2..80248a3 100644 --- a/bot.go +++ b/bot.go @@ -2,6 +2,7 @@ package bot import ( + "errors" "log" "math/rand" "time" @@ -16,7 +17,7 @@ const ( // MsgBuffer is the max number of messages which can be buffered // while waiting to flush them to the chat service. - MsgBuffer = 10 + MsgBuffer = 100 ) // Bot handles the bot instance @@ -24,11 +25,8 @@ type Bot struct { handlers *Handlers cron *cron.Cron disabledCmds []string - - synchronousMessageSending bool - - msgsToSend chan *responseMessage - done chan struct{} + msgsToSend chan responseMessage + done chan struct{} } type responseMessage struct { @@ -39,22 +37,35 @@ type responseMessage struct { // ResponseHandler must be implemented by the protocol to handle the bot responses type ResponseHandler func(target, message string, sender *User) +// ErrorHandler will be called when an error happens +type ErrorHandler func(msg string, err error) + // Handlers that must be registered to receive callbacks from the bot type Handlers struct { Response ResponseHandler + Errored ErrorHandler +} + +func logErrorHandler(msg string, err error) { + log.Printf("%s: %s", msg, err.Error()) } // New configures a new bot instance func New(h *Handlers) *Bot { + if h.Errored == nil { + h.Errored = logErrorHandler + } + b := &Bot{ handlers: h, cron: cron.New(), - msgsToSend: make(chan *responseMessage, MsgBuffer), + msgsToSend: make(chan responseMessage, MsgBuffer), done: make(chan struct{}), } + // Launch the background goroutine that isolates the possibly non-threadsafe // message sending logic of the underlying transport layer. - go b.messageSender() + go b.processMessages() b.startPeriodicCommands() return b @@ -67,7 +78,7 @@ func (b *Bot) startPeriodicCommands() { for _, channel := range config.Channels { message, err := config.CmdFunc(channel) if err != nil { - log.Print("Periodic command failed ", err) + b.errored("Periodic command failed ", err) } else if message != "" { b.SendMessage(channel, message, nil) } @@ -113,25 +124,28 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us // SendMessage queues a message for a target recipient, optionally from a particular sender. func (b *Bot) SendMessage(target string, message string, sender *User) { - if b.synchronousMessageSending { - b.handlers.Response(target, message, sender) - return - } - select { - case b.msgsToSend <- &responseMessage{ - target, message, sender, - }: + case b.msgsToSend <- responseMessage{target, message, sender}: default: - log.Printf("Failed to queue message to send. Must be busy.") + b.errored("Failed to queue message to send.", errors.New("Too busy")) + } +} + +func (b *Bot) sendResponse(target, message string, sender *User) { + b.handlers.Response(target, message, sender) +} + +func (b *Bot) errored(msg string, err error) { + if b.handlers.Errored != nil { + b.handlers.Errored(msg, err) } } -func (b *Bot) messageSender() { +func (b *Bot) processMessages() { for { select { case msg := <-b.msgsToSend: - b.handlers.Response(msg.target, msg.message, msg.sender) + b.sendResponse(msg.target, msg.message, msg.sender) case <-b.done: return } diff --git a/cmd.go b/cmd.go index 5db684e..dbe440c 100644 --- a/cmd.go +++ b/cmd.go @@ -1,8 +1,8 @@ package bot import ( + "errors" "fmt" - "log" "sync" ) @@ -213,14 +213,14 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { case pv1: result, err := cmdFunc.PassiveFuncV1(cmd) if err != nil { - log.Println(err) + b.errored(fmt.Sprintf("Error executing %s", cmdFunc.Cmd), err) } else { b.SendMessage(cmd.Channel, result, cmd.User) } case pv2: result, err := cmdFunc.PassiveFuncV2(cmd) if err != nil { - log.Println(err) + b.errored(fmt.Sprintf("Error executing %s", cmdFunc.Cmd), err) return } for { @@ -253,7 +253,7 @@ func (b *Bot) handleCmd(c *Cmd) { cmd := commands[c.Command] if cmd == nil { - log.Printf("Command not found %v", c.Command) + b.errored(fmt.Sprintf("Command not found %v", c.Command), errors.New("Command not found")) return } @@ -296,7 +296,7 @@ func (b *Bot) handleCmd(c *Cmd) { func (b *Bot) checkCmdError(err error, c *Cmd) { if err != nil { errorMsg := fmt.Sprintf(errorExecutingCommand, c.Command, err.Error()) - log.Printf(errorMsg) + b.errored(errorMsg, err) b.SendMessage(c.Channel, errorMsg, c.User) } } diff --git a/cmd_test.go b/cmd_test.go index 5371d25..ba374d1 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -7,12 +7,16 @@ import ( "sort" "strings" "testing" + "time" ) var ( - channel string - replies []string - user *User + channel string + replies chan string + cmdError chan string + user *User + msgs []string + errs []string ) const ( @@ -22,29 +26,54 @@ const ( cmdExampleArgs = "arg1 arg2" ) +func waitMessages(t *testing.T, count int, errorCount int) { + for { + select { + case reply := <-replies: + msgs = append(msgs, reply) + case err := <-cmdError: + errs = append(errs, err) + case <-time.After(1 * time.Second): + t.Error("Timeout waiting for messages") + t.Errorf("msgs received: %v", msgs) + t.Errorf("errs received: %v", errs) + t.Fatal() + } + if len(msgs) == count && len(errs) == errorCount { + return + } + } +} + func responseHandler(target string, message string, sender *User) { channel = target user = sender - replies = append(replies, message) + replies <- message +} + +func errorHandler(msg string, err error) { + cmdError <- fmt.Sprintf("%s: %s", msg, err) } -func resetResponses() { +func reset() { channel = "" user = &User{Nick: ""} - replies = []string{} + replies = make(chan string, 10) + cmdError = make(chan string, 10) + msgs = []string{} + errs = []string{} commands = make(map[string]*customCommand) + periodicCommands = make(map[string]PeriodicConfig) + passiveCommands = make(map[string]*customCommand) } func newBot() *Bot { return New(&Handlers{ Response: responseHandler, + Errored: errorHandler, }) } -func resetRegisteredPeriodicCommands() { - periodicCommands = make(map[string]PeriodicConfig) -} - func registerValidCommand() { RegisterCommand(cmd, cmdDescription, cmdExampleArgs, func(c *Cmd) (string, error) { @@ -53,8 +82,7 @@ func registerValidCommand() { } func TestPeriodicCommands(t *testing.T) { - resetResponses() - resetRegisteredPeriodicCommands() + reset() RegisterPeriodicCommand("morning", PeriodicConfig{ CronSpec: "0 0 08 * * mon-fri", @@ -62,7 +90,6 @@ func TestPeriodicCommands(t *testing.T) { CmdFunc: func(channel string) (string, error) { return "ok " + channel, nil }, }) b := New(&Handlers{Response: responseHandler}) - b.synchronousMessageSending = true defer b.Close() entries := b.cron.Entries() @@ -75,17 +102,14 @@ func TestPeriodicCommands(t *testing.T) { entries[0].Job.Run() - if len(replies) != 1 { - t.Fatal("Should have one reply in the channel") - } - if replies[0] != "ok #channel" { + waitMessages(t, 1, 0) + + if msgs[0] != "ok #channel" { t.Fatal("Invalid reply") } } - func TestMultiplePeriodicCommands(t *testing.T) { - resetResponses() - resetRegisteredPeriodicCommands() + reset() RegisterPeriodicCommand("morning", PeriodicConfig{ CronSpec: "0 0 08 * * mon-fri", @@ -99,7 +123,6 @@ func TestMultiplePeriodicCommands(t *testing.T) { CmdFunc: func(channel string) (string, error) { return "ok_afternoon " + channel, nil }, }) b := New(&Handlers{Response: responseHandler}) - b.synchronousMessageSending = true defer b.Close() entries := b.cron.Entries() @@ -116,28 +139,29 @@ func TestMultiplePeriodicCommands(t *testing.T) { entries[0].Job.Run() entries[1].Job.Run() - if len(replies) != 2 { + waitMessages(t, 2, 0) + + if len(msgs) != 2 { t.Fatal("Should have two replies in the channel") } - if replies[0] != "ok_morning #channel" { - t.Fatal("Invalid reply in first cron job") + sort.Strings(msgs) + if msgs[0] != "ok_afternoon #channel" { + t.Fatal("Invalid reply in afternoon cron job") } - if replies[1] != "ok_afternoon #channel" { - t.Fatal("Invalid reply in second cron job") + if msgs[1] != "ok_morning #channel" { + t.Fatalf("Invalid reply in morning cron job.") } } func TestErroredPeriodicCommand(t *testing.T) { - resetResponses() - resetRegisteredPeriodicCommands() + reset() RegisterPeriodicCommand("bugged", PeriodicConfig{ CronSpec: "0 0 08 * * mon-fri", Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "bug", errors.New("error") }, }) - b := New(&Handlers{Response: responseHandler}) - b.synchronousMessageSending = true + b := newBot() defer b.Close() entries := b.cron.Entries() @@ -147,19 +171,20 @@ func TestErroredPeriodicCommand(t *testing.T) { } entries[0].Job.Run() + waitMessages(t, 0, 1) - if len(replies) != 0 { - t.Fatal("Should not have a reply in the channel") + if len(msgs) != 0 { + t.Error("Should not have a reply in the channel") + } + if len(errs) != 1 { + t.Error("Expected 1 error") } } func TestDisabledCommands(t *testing.T) { - resetResponses() + reset() commands = make(map[string]*customCommand) - b := New(&Handlers{ - Response: responseHandler, - }) - b.synchronousMessageSending = true + b := newBot() RegisterCommand("cmd", "", "", func(c *Cmd) (string, error) { @@ -173,51 +198,52 @@ func TestDisabledCommands(t *testing.T) { b.Disable([]string{"cmd"}) b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) - if len(replies) != 0 { + + time.Sleep(100) + if len(msgs) != 0 { t.Fatal("Should not execute disabled active commands") } b.Disable([]string{"passive"}) b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "regular message"}, &User{Nick: "user"}) - if len(replies) != 0 { + time.Sleep(100) + if len(msgs) != 0 { t.Fatal("Should not execute disabled passive commands") } } func TestCommandNotRegistered(t *testing.T) { - resetResponses() - + reset() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!not_a_cmd"}, &User{}) - if len(replies) != 0 { + time.Sleep(100) + + if len(msgs) != 0 { t.Fatal("Should not reply if a command is not found") } } func TestInvalidCmdArgs(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd \"invalid arg"}, &User{Nick: "user"}) + waitMessages(t, 1, 0) + if channel != "#go-bot" { t.Error("Should reply to #go-bot channel") } - if len(replies) != 1 { - t.Fatal("Invalid reply") - } - if !strings.HasPrefix(replies[0], "Error parsing") { + if !strings.HasPrefix(msgs[0], "Error parsing") { t.Fatal("Should reply with an error message") } } func TestErroredCmd(t *testing.T) { - resetResponses() + reset() cmdError := errors.New("error") RegisterCommand("cmd", "", "", func(c *Cmd) (string, error) { @@ -225,35 +251,34 @@ func TestErroredCmd(t *testing.T) { }) b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + waitMessages(t, 1, 1) + if channel != "#go-bot" { t.Fatal("Invalid channel") } - if len(replies) != 1 { - t.Fatal("Invalid reply") - } - if replies[0] != fmt.Sprintf(errorExecutingCommand, "cmd", cmdError.Error()) { + if msgs[0] != fmt.Sprintf(errorExecutingCommand, "cmd", cmdError.Error()) { t.Fatal("Reply should contain the error message") } + if len(errs) != 1 { + t.Error("Expected the command to return an error") + } } func TestValidCmdOnChannel(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + waitMessages(t, 1, 0) + if channel != "#go-bot" { t.Fatal("Command called on channel should reply to channel") } - if len(replies) != 1 { - t.Fatal("Should have one reply on channel") - } - if replies[0] != expectedMsg { + if msgs[0] != expectedMsg { t.Fatal("Invalid command reply") } } @@ -270,57 +295,59 @@ func TestChannelData(t *testing.T) { } func TestHelpWithNoArgs(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help"}, &User{Nick: "user"}) + waitMessages(t, 2, 0) + expectedReply := []string{ fmt.Sprintf(helpAboutCommand, CmdPrefix), fmt.Sprintf(availableCommands, "cmd"), } - if !reflect.DeepEqual(replies, expectedReply) { - t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, replies) + if !reflect.DeepEqual(msgs, expectedReply) { + t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, msgs) } } func TestDisableHelp(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.Disable([]string{"help"}) b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help"}, &User{Nick: "user"}) + time.Sleep(100) + if len(replies) > 0 { t.Fatalf("Should not execute help after disabling it") } } func TestHelpForACommand(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd"}, &User{Nick: "user"}) + waitMessages(t, 2, 0) + expectedReply := []string{ fmt.Sprintf(helpDescripton, cmdDescription), fmt.Sprintf(helpUsage, CmdPrefix, cmd, cmdExampleArgs), } - if !reflect.DeepEqual(replies, expectedReply) { - t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, replies) + if !reflect.DeepEqual(msgs, expectedReply) { + t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, msgs) } } func TestHelpWithNonExistingCommand(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help not_a_cmd"}, &User{Nick: "user"}) expectedReply := []string{ @@ -328,28 +355,28 @@ func TestHelpWithNonExistingCommand(t *testing.T) { fmt.Sprintf(availableCommands, "cmd"), } - if !reflect.DeepEqual(replies, expectedReply) { - t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, replies) + waitMessages(t, 2, 0) + + if !reflect.DeepEqual(msgs, expectedReply) { + t.Fatalf("Invalid reply. Expected %v got %v", expectedReply, msgs) } } func TestHelpWithInvalidArgs(t *testing.T) { - resetResponses() + reset() registerValidCommand() b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!help cmd \"invalid arg"}, &User{Nick: "user"}) - if len(replies) != 1 { - t.Fatal("Invalid reply") - } - if !strings.HasPrefix(replies[0], "Error parsing") { + waitMessages(t, 1, 0) + + if !strings.HasPrefix(msgs[0], "Error parsing") { t.Fatal("Should reply with an error message") } } func TestCmdV2(t *testing.T) { - resetResponses() + reset() RegisterCommandV2("cmd", "", "", func(c *Cmd) (CmdResult, error) { return CmdResult{ @@ -358,74 +385,104 @@ func TestCmdV2(t *testing.T) { }) b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + waitMessages(t, 1, 0) + if channel != "#channel" { t.Error("Wrong channel") } - if !reflect.DeepEqual([]string{"message"}, replies) { + if !reflect.DeepEqual([]string{"message"}, msgs) { t.Error("Invalid reply") } } func TestCmdV2WithoutSpecifyingChannel(t *testing.T) { - resetResponses() + reset() RegisterCommandV2("cmd", "", "", func(c *Cmd) (CmdResult, error) { return CmdResult{Message: "message"}, nil }) b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + waitMessages(t, 1, 0) + if channel != "#go-bot" { t.Error("Should reply to original channel if no channel is returned") } } func TestPassiveCommand(t *testing.T) { - resetResponses() - + reset() passiveCommands = make(map[string]*customCommand) - - echo := func(cmd *PassiveCmd) (string, error) { - return cmd.Raw, nil - } - ping := func(cmd *PassiveCmd) (string, error) { - return "pong", nil - } - errored := func(cmd *PassiveCmd) (string, error) { - return "", errors.New("error") - } + echo := func(cmd *PassiveCmd) (string, error) { return cmd.Raw, nil } + ping := func(cmd *PassiveCmd) (string, error) { return "pong", nil } + errored := func(cmd *PassiveCmd) (string, error) { return "", errors.New("error") } RegisterPassiveCommand("echo", echo) RegisterPassiveCommand("ping", ping) RegisterPassiveCommand("errored", errored) b := newBot() - b.synchronousMessageSending = true b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + waitMessages(t, 2, 1) + if channel != "#go-bot" { t.Error("Invalid channel") } - if len(replies) != 2 { + if len(msgs) != 2 { t.Fatal("Invalid reply") } + if len(errs) != 1 { + t.Error("Expected 1 error") + } - sort.Strings(replies) - if replies[0] != "pong" { + sort.Strings(msgs) + if msgs[0] != "pong" { t.Error("ping command not executed") } - if replies[1] != "test" { + if msgs[1] != "test" { t.Error("echo command not executed") } } +func TestPassiveCommandV2(t *testing.T) { + reset() + result := CmdResultV3{ + Channel: "#channel", + Message: make(chan string), + Done: make(chan bool)} + + ping := func(cmd *PassiveCmd) (CmdResultV3, error) { return result, nil } + errored := func(cmd *PassiveCmd) (CmdResultV3, error) { return CmdResultV3{}, errors.New("error") } + + RegisterPassiveCommandV2("ping", ping) + RegisterPassiveCommandV2("errored", errored) + + b := newBot() + go b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + result.Message <- "pong" + result.Done <- true + + waitMessages(t, 1, 1) + + if channel != "#channel" { + t.Error("Invalid channel") + } + if len(msgs) != 1 { + t.Fatal("Invalid reply") + } + + if msgs[0] != "pong" { + t.Error("ping command not executed") + } +} + func TestCmdV3(t *testing.T) { - resetResponses() + reset() result := CmdResultV3{ Channel: "#channel", Message: make(chan string), @@ -436,21 +493,22 @@ func TestCmdV3(t *testing.T) { }) b := newBot() - b.synchronousMessageSending = true go b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) result.Message <- "message" result.Done <- true + waitMessages(t, 1, 0) + if channel != "#channel" { t.Error("Wrong channel") } - if !reflect.DeepEqual([]string{"message"}, replies) { + if !reflect.DeepEqual([]string{"message"}, msgs) { t.Error("Invalid reply") } } func TestCmdV3WithoutSpecifyingChannel(t *testing.T) { - resetResponses() + reset() result := CmdResultV3{ Message: make(chan string), Done: make(chan bool)} @@ -460,15 +518,16 @@ func TestCmdV3WithoutSpecifyingChannel(t *testing.T) { }) b := newBot() - b.synchronousMessageSending = true go b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) result.Message <- "message" result.Done <- true + waitMessages(t, 1, 0) + if channel != "#go-bot" { t.Error("Should reply to original channel if no channel is returned") } - if !reflect.DeepEqual([]string{"message"}, replies) { + if !reflect.DeepEqual([]string{"message"}, msgs) { t.Error("Invalid reply") } } diff --git a/slack/slack.go b/slack/slack.go index daa3521..92ab4d3 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -8,6 +8,8 @@ import ( "github.com/nlopes/slack" ) +// MessageFilter allows implementing a filter function to transform the messages +// before sending to the channel, it is run before the bot sends the message to slack type MessageFilter func(string, *bot.User) (string, slack.PostMessageParameters) var ( @@ -105,6 +107,8 @@ func ownMessage(UserID string) bool { return botUserID == UserID } +// RunWithFilter executes the bot and sets up a message filter which will +// receive all the messages before they are sent to slack func RunWithFilter(token string, customMessageFilter MessageFilter) { if customMessageFilter == nil { panic("A valid message filter must be provided.") diff --git a/telegram/telegram.go b/telegram/telegram.go index c9f520b..cbda277 100644 --- a/telegram/telegram.go +++ b/telegram/telegram.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/go-chat-bot/bot" - "gopkg.in/telegram-bot-api.v3" + tgbotapi "gopkg.in/telegram-bot-api.v3" ) var ( @@ -21,7 +21,6 @@ func responseHandler(target string, message string, sender *bot.User) { return } msg := tgbotapi.NewMessage(id, message) - msg.ReplyToMessageID = msg.ReplyToMessageID tg.Send(msg) } From 6f6277662d40d5ae3592e3031542e068b1feee15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Wed, 30 May 2018 20:18:44 -0300 Subject: [PATCH 08/42] Add debug app. Fixes #36 (#75) --- README.md | 6 +++++ debug/main.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 debug/main.go diff --git a/README.md b/README.md index db257a8..5ab8fcf 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ Please see the [plugins repository](https://github.com/go-chat-bot/plugins) for You can also write your own, it's really simple. +## Debug + +To test the bot, use the [debug](https://github.com/go-chat-bot/bot/tree/master/debug) console app. + +To test your plugin, add your plugin to `debug/main.go` import list, then build and execute the debug app. + ## Protocols ### Slack diff --git a/debug/main.go b/debug/main.go new file mode 100644 index 0000000..53ab159 --- /dev/null +++ b/debug/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "github.com/go-chat-bot/bot" + _ "github.com/go-chat-bot/plugins-br/cnpj" + _ "github.com/go-chat-bot/plugins-br/cotacao" + _ "github.com/go-chat-bot/plugins-br/cpf" + _ "github.com/go-chat-bot/plugins-br/dilma" + _ "github.com/go-chat-bot/plugins-br/lula" + _ "github.com/go-chat-bot/plugins-br/megasena" + _ "github.com/go-chat-bot/plugins/9gag" + _ "github.com/go-chat-bot/plugins/catfacts" + _ "github.com/go-chat-bot/plugins/catgif" + _ "github.com/go-chat-bot/plugins/chucknorris" + _ "github.com/go-chat-bot/plugins/cmd" + _ "github.com/go-chat-bot/plugins/crypto" + _ "github.com/go-chat-bot/plugins/encoding" + _ "github.com/go-chat-bot/plugins/example" + _ "github.com/go-chat-bot/plugins/gif" + _ "github.com/go-chat-bot/plugins/godoc" + _ "github.com/go-chat-bot/plugins/guid" + _ "github.com/go-chat-bot/plugins/jira" + _ "github.com/go-chat-bot/plugins/puppet" + _ "github.com/go-chat-bot/plugins/treta" + _ "github.com/go-chat-bot/plugins/uptime" + _ "github.com/go-chat-bot/plugins/url" + _ "github.com/go-chat-bot/plugins/web" +) + +func responseHandler(target string, message string, sender *bot.User) { + if message == "" { + return + } + fmt.Println(fmt.Sprintf("%s: %s", sender.Nick, message)) +} + +func main() { + b := bot.New(&bot.Handlers{ + Response: responseHandler, + }) + + fmt.Println("Type a command or !help for available commands...") + + for { + r := bufio.NewReader(os.Stdin) + + input, err := r.ReadString('\n') + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + b.MessageReceived( + &bot.ChannelData{ + Protocol: "debug", + Server: "", + Channel: "console", + IsPrivate: true, + }, + &bot.Message{Text: input}, + &bot.User{ID: "id", RealName: "Debug Console", Nick: "bot", IsBot: false}) + } +} From 40d3805459e130fa0974fc4b9893b51a519cc2fb Mon Sep 17 00:00:00 2001 From: Stanislav Ochotnicky Date: Sun, 15 Jul 2018 16:44:35 +0200 Subject: [PATCH 09/42] Fix indent in README.md IRC example Presumably the initial tab was unintented --- README.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5ab8fcf..7e30c3c 100644 --- a/README.md +++ b/README.md @@ -67,26 +67,27 @@ To deploy your own go-bot to IRC, you need to: Here is a full example: ```Go package main - import ( - "github.com/go-chat-bot/bot/irc" - _ "github.com/go-chat-bot/plugins/catfacts" - _ "github.com/go-chat-bot/plugins/catgif" - _ "github.com/go-chat-bot/plugins/chucknorris" - // Import all the commands you wish to use - "os" - "strings" - ) - - func main() { - irc.Run(&irc.Config{ - Server: os.Getenv("IRC_SERVER"), - Channels: strings.Split(os.Getenv("IRC_CHANNELS"), ","), - User: os.Getenv("IRC_USER"), - Nick: os.Getenv("IRC_NICK"), - Password: os.Getenv("IRC_PASSWORD"), - UseTLS: true, - Debug: os.Getenv("DEBUG") != "",}) - } + +import ( + "github.com/go-chat-bot/bot/irc" + _ "github.com/go-chat-bot/plugins/catfacts" + _ "github.com/go-chat-bot/plugins/catgif" + _ "github.com/go-chat-bot/plugins/chucknorris" + // Import all the commands you wish to use + "os" + "strings" +) + +func main() { + irc.Run(&irc.Config{ + Server: os.Getenv("IRC_SERVER"), + Channels: strings.Split(os.Getenv("IRC_CHANNELS"), ","), + User: os.Getenv("IRC_USER"), + Nick: os.Getenv("IRC_NICK"), + Password: os.Getenv("IRC_PASSWORD"), + UseTLS: true, + Debug: os.Getenv("DEBUG") != "",}) +} ``` To join channels with passwords just put the password after the channel name separated by a space: From 506a7ea68f8cad1df0ea214e268b0b3ce3fbec6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sat, 6 Oct 2018 10:57:52 -0300 Subject: [PATCH 10/42] Add go 1.11 modules support (#81) --- go.mod | 23 +++++++++++++++++++++++ go.sum | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a1956dd --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module github.com/go-chat-bot/bot + +require ( + github.com/andygrunwald/go-jira v1.5.0 // indirect + github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 // indirect + github.com/cloudfoundry/gosigar v1.1.0 // indirect + github.com/fatih/structs v1.0.0 // indirect + github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 + github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc + github.com/google/go-querystring v1.0.0 // indirect + github.com/gorilla/websocket v1.4.0 // indirect + github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect + github.com/mattn/go-shellwords v1.0.3 + github.com/nlopes/slack v0.4.0 + github.com/pkg/errors v0.8.0 // indirect + github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f + github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 + github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 + github.com/trivago/tgo v1.0.5 // indirect + golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e // indirect + gopkg.in/telegram-bot-api.v3 v3.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a3aa8a --- /dev/null +++ b/go.sum @@ -0,0 +1,38 @@ +github.com/andygrunwald/go-jira v1.5.0 h1:/1CyYLNdwus7TvB/DHyD3udb52K12aYL9m7WaGAO9m4= +github.com/andygrunwald/go-jira v1.5.0/go.mod h1:yNYQrX3nGSrVdcVsM2mWz2pm7tTeDtYfRyVEkc3VUiY= +github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8= +github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I= +github.com/cloudfoundry/gosigar v1.1.0 h1:V/dVCzhKOdIU3WRB5inQU20s4yIgL9Dxx/Mhi0SF8eM= +github.com/cloudfoundry/gosigar v1.1.0/go.mod h1:3qLfc2GlfmwOx2+ZDaRGH3Y9fwQ0sQeaAleo2GV5pH0= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 h1:qNYjVQnDwznjLk+OnNdczA5SXwEa/RwjPTZSQCKofF4= +github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= +github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc h1:v/poG4Y4O/z1cUm2cWxiIkFFgRsT3Fe1u1A33evx89g= +github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCGOsuuMnn7P2X9dGh3meUNASx6kHIpe7A= +github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= +github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA= +github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaNHbtrHW9ZuzrilETIabr9jPNj3Zs= +github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= +github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= +github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQMTscCQ7VH3JAVuxb/pe53v2LBiA7z8= +github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3/go.mod h1:QYOctLs5qEsaIrA/PKEc4YqAv2SozbxNEX0vMPs84p4= +github.com/trivago/tgo v1.0.5 h1:ihzy8zFF/LPsd8oxsjYOE8CmyOTNViyFCy0EaFreUIk= +github.com/trivago/tgo v1.0.5/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/telegram-bot-api.v3 v3.0.0 h1:Y6QmqOMwRKv5NUdlvzEBtEZChjsrqdTS6O858cvuCww= +gopkg.in/telegram-bot-api.v3 v3.0.0/go.mod h1:WxP4rAHcQNrXhQLGIK9aVLkpygV4Qq8YS3yjjJ/0VLA= From 563ab6e7b4946460ce8f65a6b8c0648cdb52159b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sat, 6 Oct 2018 11:49:24 -0300 Subject: [PATCH 11/42] Migrates build to circle 2.0 (#83) --- .circleci/config.yml | 13 +++++++++++++ circle.yml | 7 ------- 2 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..6afa803 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,13 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang:1.11 + environment: + GO111MODULE: "on" + working_directory: /go/src/github.com/go-chat-bot/bot + steps: + - checkout + - run: go test -v -cover -race -coverprofile=coverage.out + - run: go get github.com/mattn/goveralls + - run: goveralls -coverprofile=coverage.out -service=circle-ci \ No newline at end of file diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 5fe34fb..0000000 --- a/circle.yml +++ /dev/null @@ -1,7 +0,0 @@ -test: - pre: - - go get github.com/mattn/goveralls - override: - - go test -v -cover -race -coverprofile=/home/ubuntu/coverage.out - post: - - goveralls -coverprofile=/home/ubuntu/coverage.out -service=circle-ci From 05926407d1800e1925b15ccb4ca9225e75806fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sat, 6 Oct 2018 11:54:36 -0300 Subject: [PATCH 12/42] Adds debug instructions to README (#82) --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7e30c3c..f3fb39b 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,32 @@ IRC, Slack & Telegram bot written in [Go][go] using [go-ircevent][go-ircevent] f ![2016-01-17 11 21 38 036](https://cloud.githubusercontent.com/assets/1084729/12377689/5bf7d5f2-bd0d-11e5-87d9-525481f01c3a.gif) -## See the bot in action on IRC - -To see the bot in action, send a private message to **go-bot** on Freenode or join the channel **#go-bot @ irc.freenode.org**. - -Type `!help` in the channel or send `!help` in private message. - ## Plugins Please see the [plugins repository](https://github.com/go-chat-bot/plugins) for a complete list of plugins. You can also write your own, it's really simple. -## Debug +## Compiling, and testing the bot and plugins (Debug) + +This project uses the new [Go 1.11 modules](https://github.com/golang/go/wiki/Modules) if you have Go 1.11 installed, just clone the project and follow the instructions bellow, when you build Go will automatically download all dependencies. To test the bot, use the [debug](https://github.com/go-chat-bot/bot/tree/master/debug) console app. -To test your plugin, add your plugin to `debug/main.go` import list, then build and execute the debug app. +- Clone this repository or use `go get github.com/go-chat-bot` +- Build everything: `go build ./...` +- Build and execute the debug app: + - `cd debug` + - `go build` + - `./debug` +- This will open a console where you can type commands +- Type `!help` to see the list of available commands + +### Testing your plugin + +- Add your plugin to `debug/main.go` import list +- Build the debug app +- Execute it and test with the interactive console ## Protocols From 97d0d74a8b652444c77bc8c8f24b03ce1953d71b Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Sat, 6 Oct 2018 12:04:03 -0400 Subject: [PATCH 13/42] Add Case Insensitivity to commands (#85) * Change parser tests to use sub tests --- .circleci/config.yml | 2 +- parser.go | 2 +- parser_test.go | 138 +++++++++++++++++++++++++++---------------- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6afa803..a16af01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,4 +10,4 @@ jobs: - checkout - run: go test -v -cover -race -coverprofile=coverage.out - run: go get github.com/mattn/goveralls - - run: goveralls -coverprofile=coverage.out -service=circle-ci \ No newline at end of file + - run: goveralls -coverprofile=coverage.out -service=circle-ci -repotoken=$COVERALLS_TOKEN \ No newline at end of file diff --git a/parser.go b/parser.go index b3debbd..3aedba3 100644 --- a/parser.go +++ b/parser.go @@ -35,7 +35,7 @@ func parse(s string, channel *ChannelData, user *User) (*Cmd, error) { // get the command pieces := strings.SplitN(c.Message, " ", 2) - c.Command = pieces[0] + c.Command = strings.ToLower(pieces[0]) if len(pieces) > 1 { // get the arguments and remove extra spaces diff --git a/parser_test.go b/parser_test.go index 435d127..2cfc26d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -11,6 +11,9 @@ func TestParser(t *testing.T) { cmdWithoutArgs := CmdPrefix + "cmd" cmdWithArgs := CmdPrefix + "cmd arg1 arg2 " cmdWithQuotes := CmdPrefix + "cmd \"arg1 arg2\"" + cmdMixedCaseWithoutArgs := CmdPrefix + "Cmd" + cmdMixedCaseWithArgs := CmdPrefix + "Cmd arg1 arg2 " + cmdMixedCaseWithQuotes := CmdPrefix + "Cmd \"arg1 arg2\"" tests := []struct { msg string @@ -50,62 +53,95 @@ func TestParser(t *testing.T) { Args: []string{"arg1 arg2"}, MessageData: &Message{Text: strings.TrimLeft("cmd \"arg1 arg2\"", CmdPrefix)}, }}, + {cmdMixedCaseWithoutArgs, &Cmd{ + Raw: cmdMixedCaseWithoutArgs, + Command: "cmd", + Channel: channel.Channel, + ChannelData: channel, + User: user, + Message: "Cmd", + MessageData: &Message{Text: strings.TrimLeft("Cmd", CmdPrefix)}, + }}, + {cmdMixedCaseWithArgs, &Cmd{ + Raw: cmdMixedCaseWithArgs, + Command: "cmd", + Channel: channel.Channel, + ChannelData: channel, + User: user, + Message: "Cmd arg1 arg2", + RawArgs: "arg1 arg2", + Args: []string{"arg1", "arg2"}, + MessageData: &Message{Text: strings.TrimLeft("Cmd arg1 arg2", CmdPrefix)}, + }}, + {cmdMixedCaseWithQuotes, &Cmd{ + Raw: cmdMixedCaseWithQuotes, + Command: "cmd", + Channel: channel.Channel, + ChannelData: channel, + User: user, + Message: "Cmd \"arg1 arg2\"", + RawArgs: "\"arg1 arg2\"", + Args: []string{"arg1 arg2"}, + MessageData: &Message{Text: strings.TrimLeft("Cmd \"arg1 arg2\"", CmdPrefix)}, + }}, } for _, test := range tests { - cmd, _ := parse(test.msg, channel, user) - if test.expected != nil && cmd != nil { - if test.expected.Raw != cmd.Raw { - t.Errorf("Expected Raw:\n%#v\ngot:\n%#v", test.expected.Raw, cmd.Raw) - } - if test.expected.Channel != cmd.Channel { - t.Errorf("Expected Channel:\n%#v\ngot:\n%#v", test.expected.Channel, cmd.Channel) - } - if test.expected.Message != cmd.Message { - t.Errorf("Expected Message:\n%#v\ngot:\n%#v", test.expected.Message, cmd.Message) - } - if test.expected.Command != cmd.Command { - t.Errorf("Expected Command:\n%#v\ngot:\n%#v", test.expected.Command, cmd.Command) - } - if test.expected.RawArgs != cmd.RawArgs { - t.Errorf("Expected RawArgs:\n%#v\ngot:\n%#v", test.expected.RawArgs, cmd.RawArgs) - } - if test.expected.ChannelData.Protocol != cmd.ChannelData.Protocol { - t.Errorf("Expected ChannelData.Protocol:\n%#v\ngot:\n%#v", test.expected.ChannelData.Protocol, cmd.ChannelData.Protocol) - } - if test.expected.ChannelData.Server != cmd.ChannelData.Server { - t.Errorf("Expected ChannelData.Server:\n%#v\ngot:\n%#v", test.expected.ChannelData.Server, cmd.ChannelData.Server) - } - if test.expected.ChannelData.Channel != cmd.ChannelData.Channel { - t.Errorf("Expected ChannelData.Channel:\n%#v\ngot:\n%#v", test.expected.ChannelData.Channel, cmd.ChannelData.Channel) - } - if test.expected.ChannelData.IsPrivate != cmd.ChannelData.IsPrivate { - t.Errorf("Expected ChannelData.IsPrivate:\n%#v\ngot:\n%#v", test.expected.ChannelData.IsPrivate, cmd.ChannelData.IsPrivate) - } - if test.expected.User.ID != cmd.User.ID { - t.Errorf("Expected User.ID:\n%#v\ngot:\n%#v", test.expected.User.ID, cmd.User.ID) - } - if test.expected.User.Nick != cmd.User.Nick { - t.Errorf("Expected User.Nick:\n%#v\ngot:\n%#v", test.expected.User.Nick, cmd.User.Nick) - } - if test.expected.User.RealName != cmd.User.RealName { - t.Errorf("Expected User.RealName:\n%#v\ngot:\n%#v", test.expected.User.RealName, cmd.User.RealName) - } - if test.expected.User.IsBot != cmd.User.IsBot { - t.Errorf("Expected User.IsBot:\n%#v\ngot:\n%#v", test.expected.User.IsBot, cmd.User.IsBot) - } - if test.expected.MessageData.Text != cmd.MessageData.Text { - t.Errorf("Expected MessageData.Text:\n%#v\ngot:\n%#v", test.expected.MessageData.Text, cmd.MessageData.Text) - } - if test.expected.MessageData.IsAction != cmd.MessageData.IsAction { - t.Errorf("Expected MessageData.IsAction:\n%#v\ngot:\n%#v", test.expected.MessageData.IsAction, cmd.MessageData.IsAction) - } - for i, arg := range test.expected.Args { - if arg != cmd.Args[i] { - t.Errorf("Expected cmd.Args[]:\n%#v\ngot:\n%#v", arg, cmd.Args[i]) + t.Run(test.msg, func(t *testing.T) { + cmd, _ := parse(test.msg, channel, user) + if test.expected != nil && cmd != nil { + if test.expected.Raw != cmd.Raw { + t.Errorf("Expected Raw:\n%#v\ngot:\n%#v", test.expected.Raw, cmd.Raw) + } + if test.expected.Channel != cmd.Channel { + t.Errorf("Expected Channel:\n%#v\ngot:\n%#v", test.expected.Channel, cmd.Channel) + } + if test.expected.Message != cmd.Message { + t.Errorf("Expected Message:\n%#v\ngot:\n%#v", test.expected.Message, cmd.Message) + } + if test.expected.Command != cmd.Command { + t.Errorf("Expected Command:\n%#v\ngot:\n%#v", test.expected.Command, cmd.Command) + } + if test.expected.RawArgs != cmd.RawArgs { + t.Errorf("Expected RawArgs:\n%#v\ngot:\n%#v", test.expected.RawArgs, cmd.RawArgs) + } + if test.expected.ChannelData.Protocol != cmd.ChannelData.Protocol { + t.Errorf("Expected ChannelData.Protocol:\n%#v\ngot:\n%#v", test.expected.ChannelData.Protocol, cmd.ChannelData.Protocol) + } + if test.expected.ChannelData.Server != cmd.ChannelData.Server { + t.Errorf("Expected ChannelData.Server:\n%#v\ngot:\n%#v", test.expected.ChannelData.Server, cmd.ChannelData.Server) + } + if test.expected.ChannelData.Channel != cmd.ChannelData.Channel { + t.Errorf("Expected ChannelData.Channel:\n%#v\ngot:\n%#v", test.expected.ChannelData.Channel, cmd.ChannelData.Channel) + } + if test.expected.ChannelData.IsPrivate != cmd.ChannelData.IsPrivate { + t.Errorf("Expected ChannelData.IsPrivate:\n%#v\ngot:\n%#v", test.expected.ChannelData.IsPrivate, cmd.ChannelData.IsPrivate) + } + if test.expected.User.ID != cmd.User.ID { + t.Errorf("Expected User.ID:\n%#v\ngot:\n%#v", test.expected.User.ID, cmd.User.ID) + } + if test.expected.User.Nick != cmd.User.Nick { + t.Errorf("Expected User.Nick:\n%#v\ngot:\n%#v", test.expected.User.Nick, cmd.User.Nick) + } + if test.expected.User.RealName != cmd.User.RealName { + t.Errorf("Expected User.RealName:\n%#v\ngot:\n%#v", test.expected.User.RealName, cmd.User.RealName) + } + if test.expected.User.IsBot != cmd.User.IsBot { + t.Errorf("Expected User.IsBot:\n%#v\ngot:\n%#v", test.expected.User.IsBot, cmd.User.IsBot) + } + if test.expected.MessageData.Text != cmd.MessageData.Text { + t.Errorf("Expected MessageData.Text:\n%#v\ngot:\n%#v", test.expected.MessageData.Text, cmd.MessageData.Text) + } + if test.expected.MessageData.IsAction != cmd.MessageData.IsAction { + t.Errorf("Expected MessageData.IsAction:\n%#v\ngot:\n%#v", test.expected.MessageData.IsAction, cmd.MessageData.IsAction) + } + for i, arg := range test.expected.Args { + if arg != cmd.Args[i] { + t.Errorf("Expected cmd.Args[]:\n%#v\ngot:\n%#v", arg, cmd.Args[i]) + } } } - } + }) } } From 324e26dd1d46cf04ff38c13357ce388bd400bee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Sun, 7 Oct 2018 00:54:20 +0200 Subject: [PATCH 14/42] Initial implementation of filter commands (#79) This commit adds new type of commands which can modify outputs of other plugins. It provides a way to implement new types of plugins such as generic URL shortener or silencer. Fixes #78 --- bot.go | 5 +++++ cmd.go | 45 ++++++++++++++++++++++++++++++++++++++++++ cmd_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/bot.go b/bot.go index 80248a3..d8b555b 100644 --- a/bot.go +++ b/bot.go @@ -124,6 +124,11 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us // SendMessage queues a message for a target recipient, optionally from a particular sender. func (b *Bot) SendMessage(target string, message string, sender *User) { + message = b.executeFilterCommands(&FilterCmd{ + Target: target, + Message: message, + User: sender}) + select { case b.msgsToSend <- responseMessage{target, message, sender}: default: diff --git a/cmd.go b/cmd.go index dbe440c..40b2d53 100644 --- a/cmd.go +++ b/cmd.go @@ -39,6 +39,14 @@ type Message struct { IsAction bool // True if this was a '/me does something' message } +// FilterCmd holds information about what is output being filtered - message and +// channel where it is being sent +type FilterCmd struct { + Target string // Channel or user the message is being sent to + Message string // Message text being sent + User *User // User who triggered original message +} + // PassiveCmd holds the information which will be passed to passive commands when receiving a message type PassiveCmd struct { Raw string // Raw message sent to the channel @@ -71,6 +79,7 @@ type customCommand struct { CmdFuncV3 activeCmdFuncV3 PassiveFuncV1 passiveCmdFuncV1 PassiveFuncV2 passiveCmdFuncV2 + FilterFuncV1 filterCmdFuncV1 Description string ExampleArgs string } @@ -94,6 +103,7 @@ const ( v3 pv1 pv2 + fv1 ) const ( @@ -109,9 +119,12 @@ type activeCmdFuncV1 func(cmd *Cmd) (string, error) type activeCmdFuncV2 func(cmd *Cmd) (CmdResult, error) type activeCmdFuncV3 func(cmd *Cmd) (CmdResultV3, error) +type filterCmdFuncV1 func(cmd *FilterCmd) (string, error) + var ( commands = make(map[string]*customCommand) passiveCommands = make(map[string]*customCommand) + filterCommands = make(map[string]*customCommand) periodicCommands = make(map[string]PeriodicConfig) ) @@ -181,6 +194,23 @@ func RegisterPassiveCommandV2(command string, cmdFunc passiveCmdFuncV2) { } } +// RegisterFilterCommand adds a command that is run every time bot is about to +// send a message. The comand should be registered in the Init() func of your +// package. +// Filter commands receive message and its destination and should return +// modified version. Returning empty string prevents message being sent +// completely +// command: String used to identify the command, for internal use only (ex: silence) +// cmdFunc: Function which will be executed. It will receive the message, target +// channel and nick who triggered original message +func RegisterFilterCommand(command string, cmdFunc filterCmdFuncV1) { + filterCommands[command] = &customCommand{ + Version: fv1, + Cmd: command, + FilterFuncV1: cmdFunc, + } +} + // RegisterPeriodicCommand adds a command that is run periodically. // The command should be registered in the Init() func of your package // config: PeriodicConfig which specify CronSpec and a channel list @@ -240,6 +270,21 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { wg.Wait() } +func (b *Bot) executeFilterCommands(cmd *FilterCmd) string { + for k, filter := range filterCommands { + switch filter.Version { + case fv1: + filtered, err := filter.FilterFuncV1(cmd) + if err != nil { + b.errored(fmt.Sprintf("Error executing filter %s", k), err) + continue + } + cmd.Message = filtered + } + } + return cmd.Message +} + func (b *Bot) isDisabled(cmd string) bool { for _, c := range b.disabledCmds { if c == cmd { diff --git a/cmd_test.go b/cmd_test.go index ba374d1..6f6861a 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -65,6 +65,7 @@ func reset() { commands = make(map[string]*customCommand) periodicCommands = make(map[string]PeriodicConfig) passiveCommands = make(map[string]*customCommand) + filterCommands = make(map[string]*customCommand) } func newBot() *Bot { @@ -531,3 +532,59 @@ func TestCmdV3WithoutSpecifyingChannel(t *testing.T) { t.Error("Invalid reply") } } + +func TestFilterCommand(t *testing.T) { + reset() + passiveCommands = make(map[string]*customCommand) + ping := func(cmd *PassiveCmd) (string, error) { return "pong", nil } + modified := func(cmd *FilterCmd) (string, error) { return "PONG!", nil } + errored := func(cmd *FilterCmd) (string, error) { return "", errors.New("error") } + + RegisterPassiveCommand("ping", ping) + RegisterFilterCommand("modified", modified) + RegisterFilterCommand("errored", errored) + + b := newBot() + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + + waitMessages(t, 1, 1) + + if channel != "#go-bot" { + t.Error("Invalid channel") + } + if len(msgs) != 1 { + t.Fatal("Invalid reply") + } + if len(errs) != 1 { + t.Error("Expected 1 error") + } + + sort.Strings(msgs) + if msgs[0] != "PONG!" { + t.Error("filter command not working") + } +} + +func TestFilterCommandSilence(t *testing.T) { + reset() + passiveCommands = make(map[string]*customCommand) + ping := func(cmd *PassiveCmd) (string, error) { return "pong", nil } + silenced := func(cmd *FilterCmd) (string, error) { return "", nil } + errored := func(cmd *FilterCmd) (string, error) { return "Ignored", errors.New("error") } + + RegisterPassiveCommand("ping", ping) + RegisterFilterCommand("silenced", silenced) + RegisterFilterCommand("errored", errored) + + b := newBot() + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + + waitMessages(t, 0, 1) + + if len(msgs) != 0 { + t.Fatal("Expected no messages!") + } + if len(errs) != 1 { + t.Error("Expected 1 error") + } +} From 9060a41bb4f6aa7ef38879f90d50d9d8d961f63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sat, 6 Oct 2018 20:43:32 -0300 Subject: [PATCH 15/42] Add go report badge and fix misspells (#87) --- README.md | 2 +- cmd.go | 2 +- telegram/telegram.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f3fb39b..9d2dd04 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-bot -[![Circle CI](https://circleci.com/gh/go-chat-bot/bot/tree/master.svg?style=svg)](https://circleci.com/gh/go-chat-bot/bot/tree/master) [![GoDoc](https://godoc.org/github.com/go-chat-bot/bot?status.png)](https://godoc.org/github.com/go-chat-bot/bot) [![Coverage Status](https://coveralls.io/repos/github/go-chat-bot/bot/badge.svg?branch=master)](https://coveralls.io/github/go-chat-bot/bot?branch=master) +[![Circle CI](https://circleci.com/gh/go-chat-bot/bot/tree/master.svg?style=svg)](https://circleci.com/gh/go-chat-bot/bot/tree/master) [![GoDoc](https://godoc.org/github.com/go-chat-bot/bot?status.png)](https://godoc.org/github.com/go-chat-bot/bot) [![Coverage Status](https://coveralls.io/repos/github/go-chat-bot/bot/badge.svg?branch=master)](https://coveralls.io/github/go-chat-bot/bot?branch=master) ![Go report](https://goreportcard.com/badge/github.com/go-chat-bot/bot) IRC, Slack & Telegram bot written in [Go][go] using [go-ircevent][go-ircevent] for IRC connectivity, [nlopes/slack](https://github.com/nlopes/slack) for Slack and [Syfaro/telegram-bot-api](https://github.com/Syfaro/telegram-bot-api) for Telegram. diff --git a/cmd.go b/cmd.go index 40b2d53..1518a9c 100644 --- a/cmd.go +++ b/cmd.go @@ -220,7 +220,7 @@ func RegisterPeriodicCommand(command string, config PeriodicConfig) { } // Disable allows disabling commands that were registered. -// It is usefull when running multiple bot instances to disabled some plugins like url which +// It is useful when running multiple bot instances to disabled some plugins like url which // is already present on some protocols. func (b *Bot) Disable(cmds []string) { b.disabledCmds = append(b.disabledCmds, cmds...) diff --git a/telegram/telegram.go b/telegram/telegram.go index cbda277..0337913 100644 --- a/telegram/telegram.go +++ b/telegram/telegram.go @@ -25,7 +25,7 @@ func responseHandler(target string, message string, sender *bot.User) { tg.Send(msg) } -// Run executes the bot and connects to Telegram using the provided token. Use the debug flag if you wish to see all trafic logged +// Run executes the bot and connects to Telegram using the provided token. Use the debug flag if you wish to see all traffic logged func Run(token string, debug bool) { var err error tg, err = tgbotapi.NewBotAPI(token) From b9d139ebd3f2c627205eb1d2df01d8775035953a Mon Sep 17 00:00:00 2001 From: Fabio Gomes Date: Sat, 6 Oct 2018 20:44:42 -0300 Subject: [PATCH 16/42] Removes , from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d2dd04..b711be5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Please see the [plugins repository](https://github.com/go-chat-bot/plugins) for You can also write your own, it's really simple. -## Compiling, and testing the bot and plugins (Debug) +## Compiling and testing the bot and plugins (Debug) This project uses the new [Go 1.11 modules](https://github.com/golang/go/wiki/Modules) if you have Go 1.11 installed, just clone the project and follow the instructions bellow, when you build Go will automatically download all dependencies. From cf98806022032f1199b8e612483a21485156b169 Mon Sep 17 00:00:00 2001 From: Luka Atanasovski <1uka@users.noreply.github.com> Date: Mon, 8 Oct 2018 01:10:45 +0200 Subject: [PATCH 17/42] Add suport for parsing unicode characters (#86) --- go.mod | 1 + go.sum | 2 ++ parser.go | 5 +++-- parser_test.go | 22 ++++++++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a1956dd..a53ecae 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gorilla/websocket v1.4.0 // indirect github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect github.com/mattn/go-shellwords v1.0.3 + github.com/mozillazg/go-unidecode v0.1.0 github.com/nlopes/slack v0.4.0 github.com/pkg/errors v0.8.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f diff --git a/go.sum b/go.sum index 1a3aa8a..633d59c 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCG github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mozillazg/go-unidecode v0.1.0 h1:wAIMDf/yTexXKxT5TkctLwmClGSyuoJaZDRMclFNq8U= +github.com/mozillazg/go-unidecode v0.1.0/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA= github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= diff --git a/parser.go b/parser.go index 3aedba3..081b1ca 100644 --- a/parser.go +++ b/parser.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/mattn/go-shellwords" + unidecode "github.com/mozillazg/go-unidecode" ) var ( @@ -35,12 +36,12 @@ func parse(s string, channel *ChannelData, user *User) (*Cmd, error) { // get the command pieces := strings.SplitN(c.Message, " ", 2) - c.Command = strings.ToLower(pieces[0]) + c.Command = strings.ToLower(unidecode.Unidecode(pieces[0])) if len(pieces) > 1 { // get the arguments and remove extra spaces c.RawArgs = strings.TrimSpace(pieces[1]) - parsedArgs, err := shellwords.Parse(c.RawArgs) + parsedArgs, err := shellwords.Parse(unidecode.Unidecode(c.RawArgs)) if err != nil { return nil, errors.New("Error parsing arguments: " + err.Error()) } diff --git a/parser_test.go b/parser_test.go index 2cfc26d..c7b4f99 100644 --- a/parser_test.go +++ b/parser_test.go @@ -14,6 +14,8 @@ func TestParser(t *testing.T) { cmdMixedCaseWithoutArgs := CmdPrefix + "Cmd" cmdMixedCaseWithArgs := CmdPrefix + "Cmd arg1 arg2 " cmdMixedCaseWithQuotes := CmdPrefix + "Cmd \"arg1 arg2\"" + cmdUnicode := CmdPrefix + "Çmd" + cmdUnicodeWithArgs := CmdPrefix + "çмд Ã¥rg1 Ã¥rg2" tests := []struct { msg string @@ -84,6 +86,26 @@ func TestParser(t *testing.T) { Args: []string{"arg1 arg2"}, MessageData: &Message{Text: strings.TrimLeft("Cmd \"arg1 arg2\"", CmdPrefix)}, }}, + {cmdUnicode, &Cmd{ + Raw: cmdUnicode, + Command: "cmd", + Channel: channel.Channel, + ChannelData: channel, + User: user, + Message: "Çmd", + MessageData: &Message{Text: strings.TrimLeft("Çmd", CmdPrefix)}, + }}, + {cmdUnicodeWithArgs, &Cmd{ + Raw: cmdUnicodeWithArgs, + Command: "cmd", + Channel: channel.Channel, + ChannelData: channel, + User: user, + Message: "çмд Ã¥rg1 Ã¥rg2", + RawArgs: "Ã¥rg1 Ã¥rg2", + Args: []string{"arg1", "arg2"}, + MessageData: &Message{Text: strings.TrimLeft("çмд Ã¥rg1 Ã¥rg2", CmdPrefix)}, + }}, } for _, test := range tests { From a9659f1a3e35ca3472d16e1a3098773e1a431d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Wed, 31 Oct 2018 13:40:54 +0100 Subject: [PATCH 18/42] Add support for new periodic command type (#91) Original periodic command was run per-channel. This command can run separately and returns CmdResults that identify channels where the message should be sent to --- bot.go | 24 +++++++++++---- cmd.go | 21 +++++++++++--- cmd_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/bot.go b/bot.go index d8b555b..fb6a014 100644 --- a/bot.go +++ b/bot.go @@ -75,12 +75,24 @@ func (b *Bot) startPeriodicCommands() { for _, config := range periodicCommands { func(b *Bot, config PeriodicConfig) { b.cron.AddFunc(config.CronSpec, func() { - for _, channel := range config.Channels { - message, err := config.CmdFunc(channel) + switch config.Version { + case v1: + for _, channel := range config.Channels { + message, err := config.CmdFunc(channel) + if err != nil { + b.errored("Periodic command failed ", err) + } else if message != "" { + b.SendMessage(channel, message, nil) + } + } + case v2: + results, err := config.CmdFuncV2() if err != nil { b.errored("Periodic command failed ", err) - } else if message != "" { - b.SendMessage(channel, message, nil) + return + } + for _, result := range results { + b.SendMessage(result.Channel, result.Message, nil) } } }) @@ -125,9 +137,9 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us // SendMessage queues a message for a target recipient, optionally from a particular sender. func (b *Bot) SendMessage(target string, message string, sender *User) { message = b.executeFilterCommands(&FilterCmd{ - Target: target, + Target: target, Message: message, - User: sender}) + User: sender}) select { case b.msgsToSend <- responseMessage{target, message, sender}: diff --git a/cmd.go b/cmd.go index 1518a9c..ed522dd 100644 --- a/cmd.go +++ b/cmd.go @@ -58,9 +58,11 @@ type PassiveCmd struct { // PeriodicConfig holds a cron specification for periodically notifying the configured channels type PeriodicConfig struct { - CronSpec string // CronSpec that schedules some function - Channels []string // A list of channels to notify - CmdFunc func(channel string) (string, error) // func to be executed at the period specified on CronSpec + Version int + CronSpec string // CronSpec that schedules some function + Channels []string // A list of channels to notify, ignored for V2 + CmdFunc func(channel string) (string, error) // func to be executed at the period specified on CronSpec + CmdFuncV2 func() ([]CmdResult, error) // func v2 to be executed at the period specified on CronSpec } // User holds user id, nick and real name @@ -214,8 +216,19 @@ func RegisterFilterCommand(command string, cmdFunc filterCmdFuncV1) { // RegisterPeriodicCommand adds a command that is run periodically. // The command should be registered in the Init() func of your package // config: PeriodicConfig which specify CronSpec and a channel list -// cmdFunc: A no-arg function which gets triggered periodically +// cmdFunc: A function with single string argument (channel) which gets triggered periodically func RegisterPeriodicCommand(command string, config PeriodicConfig) { + config.Version = v1 + periodicCommands[command] = config +} + +// RegisterPeriodicCommandV2 adds a command that is run periodically. +// The command should be registered in the Init() func of your package +// config: PeriodicConfig which specifies CronSpec +// cmdFuncV2: A no-arg function which gets triggered periodically +// It should return slice of CmdResults (channel and message to send to it) +func RegisterPeriodicCommandV2(command string, config PeriodicConfig) { + config.Version = v2 periodicCommands[command] = config } diff --git a/cmd_test.go b/cmd_test.go index 6f6861a..98c4f24 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -182,6 +182,90 @@ func TestErroredPeriodicCommand(t *testing.T) { } } +func TestPeriodicCommandsV2(t *testing.T) { + reset() + RegisterPeriodicCommandV2("morning", + PeriodicConfig{ + CronSpec: "0 0 08 * * mon-fri", + CmdFuncV2: func() ([]CmdResult, error) { + ret := []CmdResult{ + {Message: "message 1", Channel: "#channel1"}, + {Message: "message 2", Channel: "#channel2"}} + return ret, nil + }, + }) + channels := make([]string, 0, 2) + b := New(&Handlers{Response: func(target string, message string, sender *User) { + channels = append(channels, target) + channel = target + user = sender + replies <- message + }}) + defer b.Close() + + entries := b.cron.Entries() + if len(entries) != 1 { + t.Fatal("Should have one cron job entry") + } + if entries[0].Next.Hour() != 8 { + t.Fatal("Cron job should be scheduled to 8am") + } + + entries[0].Job.Run() + + waitMessages(t, 2, 0) + if len(channels) != 2 { + t.Fatal("Should have 2 destinations channels", len(channels)) + } + + if msgs[0] != "message 1" { + t.Fatal("Invalid first reply") + } + + if channels[0] != "#channel1" { + t.Fatal("Invalid channel for first message", channels[0]) + } + + if msgs[1] != "message 2" { + t.Fatal("Invalid second reply") + } + + if channels[1] != "#channel2" { + t.Fatal("Invalid channel for second message", channels[1]) + } +} + +func TestErroredPeriodicCommandsV2(t *testing.T) { + reset() + RegisterPeriodicCommandV2("morning", + PeriodicConfig{ + CronSpec: "0 0 08 * * mon-fri", + CmdFuncV2: func() ([]CmdResult, error) { + return nil, errors.New("error") + }, + }) + b := newBot() + defer b.Close() + + entries := b.cron.Entries() + if len(entries) != 1 { + t.Fatal("Should have one cron job entry") + } + if entries[0].Next.Hour() != 8 { + t.Fatal("Cron job should be scheduled to 8am") + } + + entries[0].Job.Run() + + waitMessages(t, 0, 1) + if len(msgs) != 0 { + t.Error("Should not have a reply in the channel") + } + if len(errs) != 1 { + t.Error("Expected 1 error") + } +} + func TestDisabledCommands(t *testing.T) { reset() commands = make(map[string]*customCommand) From 4ab5f2f1b3d17ffa7d50714637951aaed36f363c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Wed, 31 Oct 2018 13:41:45 +0100 Subject: [PATCH 19/42] [irc] Add support for real name configuration (#93) In addition to username and nick IRC also has real name information. This can be longer string with URL to code repository for example. --- irc/irc.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/irc/irc.go b/irc/irc.go index 9fa7f3f..7e361ca 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -16,6 +16,7 @@ type Config struct { Channels []string // Channels to connect. Ex: []string{"#go-bot", "#channel mypassword"} User string // The IRC username the bot will use Nick string // The nick the bot will use + RealName string // The real name (longer string) the bot will use Password string // Server password UseTLS bool // Should connect using TLS? TLSServerName string // Must supply if UseTLS is true @@ -97,6 +98,7 @@ func SetUp(c *Config) *bot.Bot { config = c ircConn = ircevent.IRC(c.User, c.Nick) + ircConn.RealName = c.RealName ircConn.Password = c.Password ircConn.UseTLS = c.UseTLS ircConn.TLSConfig = &tls.Config{ From 98470a5844fe3c0760db3a59850a129dd4c0c21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Wed, 31 Oct 2018 14:02:51 +0100 Subject: [PATCH 20/42] Fix situation when message gets filtered out (#94) Original code tried to queue message for sending even if the filter made the content empty. This was causing at least unpredictable tests with TestFilterCommandSilence randomly failing --- bot.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot.go b/bot.go index fb6a014..7d632f4 100644 --- a/bot.go +++ b/bot.go @@ -140,6 +140,9 @@ func (b *Bot) SendMessage(target string, message string, sender *User) { Target: target, Message: message, User: sender}) + if message == "" { + return + } select { case b.msgsToSend <- responseMessage{target, message, sender}: From a6d077981efdafd6a9c14f6aa7c9f545a97ec795 Mon Sep 17 00:00:00 2001 From: Scott Albertson Date: Mon, 12 Nov 2018 02:15:17 -0800 Subject: [PATCH 21/42] Add a "Reviewed by Hound" badge (#95) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b711be5..7c34fc6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-bot -[![Circle CI](https://circleci.com/gh/go-chat-bot/bot/tree/master.svg?style=svg)](https://circleci.com/gh/go-chat-bot/bot/tree/master) [![GoDoc](https://godoc.org/github.com/go-chat-bot/bot?status.png)](https://godoc.org/github.com/go-chat-bot/bot) [![Coverage Status](https://coveralls.io/repos/github/go-chat-bot/bot/badge.svg?branch=master)](https://coveralls.io/github/go-chat-bot/bot?branch=master) ![Go report](https://goreportcard.com/badge/github.com/go-chat-bot/bot) +[![Circle CI](https://circleci.com/gh/go-chat-bot/bot/tree/master.svg?style=svg)](https://circleci.com/gh/go-chat-bot/bot/tree/master) [![GoDoc](https://godoc.org/github.com/go-chat-bot/bot?status.png)](https://godoc.org/github.com/go-chat-bot/bot) [![Coverage Status](https://coveralls.io/repos/github/go-chat-bot/bot/badge.svg?branch=master)](https://coveralls.io/github/go-chat-bot/bot?branch=master) ![Go report](https://goreportcard.com/badge/github.com/go-chat-bot/bot) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) IRC, Slack & Telegram bot written in [Go][go] using [go-ircevent][go-ircevent] for IRC connectivity, [nlopes/slack](https://github.com/nlopes/slack) for Slack and [Syfaro/telegram-bot-api](https://github.com/Syfaro/telegram-bot-api) for Telegram. From 07092db190915888631e58d957b9c5e61e25f845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Wed, 28 Nov 2018 20:50:13 +0100 Subject: [PATCH 22/42] Add hangouts chat support (#96) --- README.md | 56 +++++++++++++ go.mod | 8 ++ go.sum | 55 +++++++++++++ google-chat/chat_msg.go | 59 ++++++++++++++ google-chat/google-chat.go | 163 +++++++++++++++++++++++++++++++++++++ 5 files changed, 341 insertions(+) create mode 100644 google-chat/chat_msg.go create mode 100644 google-chat/google-chat.go diff --git a/README.md b/README.md index 7c34fc6..14c2268 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,62 @@ func main() { } ``` +### Google Chat + +To deploy your go-bot to Google Chat (also known as Hangouts Chat, not plain +Hangouts) you will first need to follow documentation to [setup pub/sub +project](https://developers.google.com/hangouts/chat/how-tos/pub-sub) in Google +Cloud. This will enable your bot to receive messages even when it is behind a +firewall. + +Condensed, the steps you will need to take are as follows: +* Create new project in google cloud console + * ID of the project will be used in Config.PubSubProject +* Create service credentials for this project + * Path to downloaded credentials file should be in env variable GOOGLE_APPLICATION_CREDENTIALS + * Choose "Pub/Sub Editor" role for the credential +* Enable Pub/Sub API in cloud console +* Create new topic in the Pub/Sub (say "google-chat") + * This is Config.TopicName +* Modify permissions on created topic so that + "chat-api-push@system.gserviceaccount.com" has Pub/Sub Publisher permissions +* Enable hangouts chat api in Cloud Console +* Go to hangouts chat API config in the Cloud Console and fill in info + * Connection settings - use Pub/Sub and fill in topic string you created + above + * Verification token is your Config.Token + +Config.SubscriptionName should be unique for each environment or you'll not +process messages correctly. If you encounter issues make sure your credentials +are correct and permissions for topics/queues are set up correctly. + +Config.WelcomeMessage is sent each time the bot joins a new room or private chat. + +Full example is here: +```Go +package main + +import ( + "os" + + "github.com/go-chat-bot/bot/google-chat" + _ "github.com/go-chat-bot/plugins/godoc" + _ "github.com/go-chat-bot/plugins/catfacts" + _ "github.com/go-chat-bot/plugins/catgif" + _ "github.com/go-chat-bot/plugins/chucknorris" +) + +func main() { + googlechat.Run(&googlechat.Config{ + PubSubProject: os.Getenv("HANGOUTS_PROJECT"), + TopicName: os.Getenv("HANGOUTS_TOPIC"), + SubscriptionName: os.Getenv("HANGOUTS_SUB"), + WelcomeMessage: os.Getenv("HANGOUTS_WELCOME"), + Token: os.Getenv("HANGOUTS_TOKEN")}) +} + +``` + ## Deploying your own bot To see an example project on how to deploy your bot, please see my own configuration: diff --git a/go.mod b/go.mod index a53ecae..4952a3a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/go-chat-bot/bot require ( + cloud.google.com/go v0.32.0 github.com/andygrunwald/go-jira v1.5.0 // indirect github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 // indirect github.com/cloudfoundry/gosigar v1.1.0 // indirect @@ -8,6 +9,7 @@ require ( github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc github.com/google/go-querystring v1.0.0 // indirect + github.com/googleapis/gax-go v2.0.0+incompatible // indirect github.com/gorilla/websocket v1.4.0 // indirect github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect github.com/mattn/go-shellwords v1.0.3 @@ -19,6 +21,12 @@ require ( github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 github.com/trivago/tgo v1.0.5 // indirect + go.opencensus.io v0.18.0 // indirect + golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e // indirect + google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93 // indirect + google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect gopkg.in/telegram-bot-api.v3 v3.0.0 ) diff --git a/go.sum b/go.sum index 633d59c..228b440 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,52 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.32.0 h1:DSt59WoyNcfAInilEpfvm2ugq8zvNyaHAm9MkzOwRQ4= +cloud.google.com/go v0.32.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/andygrunwald/go-jira v1.5.0 h1:/1CyYLNdwus7TvB/DHyD3udb52K12aYL9m7WaGAO9m4= github.com/andygrunwald/go-jira v1.5.0/go.mod h1:yNYQrX3nGSrVdcVsM2mWz2pm7tTeDtYfRyVEkc3VUiY= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry/gosigar v1.1.0 h1:V/dVCzhKOdIU3WRB5inQU20s4yIgL9Dxx/Mhi0SF8eM= github.com/cloudfoundry/gosigar v1.1.0/go.mod h1:3qLfc2GlfmwOx2+ZDaRGH3Y9fwQ0sQeaAleo2GV5pH0= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 h1:qNYjVQnDwznjLk+OnNdczA5SXwEa/RwjPTZSQCKofF4= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc h1:v/poG4Y4O/z1cUm2cWxiIkFFgRsT3Fe1u1A33evx89g= github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCGOsuuMnn7P2X9dGh3meUNASx6kHIpe7A= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mozillazg/go-unidecode v0.1.0 h1:wAIMDf/yTexXKxT5TkctLwmClGSyuoJaZDRMclFNq8U= github.com/mozillazg/go-unidecode v0.1.0/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA= github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaNHbtrHW9ZuzrilETIabr9jPNj3Zs= github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= @@ -34,7 +57,39 @@ github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQM github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3/go.mod h1:QYOctLs5qEsaIrA/PKEc4YqAv2SozbxNEX0vMPs84p4= github.com/trivago/tgo v1.0.5 h1:ihzy8zFF/LPsd8oxsjYOE8CmyOTNViyFCy0EaFreUIk= github.com/trivago/tgo v1.0.5/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93 h1:TSNRyeWkPS9y+f2F+lO1Yfn9VaFA8MGMUPvn4dXqia0= +google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b h1:WkFtVmaZoTRVoRYr0LTC9SYNhlw0X0HrVPz2OVssVm4= +google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/telegram-bot-api.v3 v3.0.0 h1:Y6QmqOMwRKv5NUdlvzEBtEZChjsrqdTS6O858cvuCww= gopkg.in/telegram-bot-api.v3 v3.0.0/go.mod h1:WxP4rAHcQNrXhQLGIK9aVLkpygV4Qq8YS3yjjJ/0VLA= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/google-chat/chat_msg.go b/google-chat/chat_msg.go new file mode 100644 index 0000000..13a5f7c --- /dev/null +++ b/google-chat/chat_msg.go @@ -0,0 +1,59 @@ +package googlechat + +import "time" + +// ChatMessage is message type from Pub/Sub events +type ChatMessage struct { + Type string `json:"type"` + EventTime time.Time `json:"eventTime"` + Token string `json:"token"` + Message struct { + Name string `json:"name"` + Sender struct { + Name string `json:"name"` + DisplayName string `json:"displayName"` + AvatarURL string `json:"avatarUrl"` + Email string `json:"email"` + Type string `json:"type"` + } `json:"sender"` + CreateTime time.Time `json:"createTime"` + Text string `json:"text"` + Thread struct { + Name string `json:"name"` + RetentionSettings struct { + State string `json:"state"` + } `json:"retentionSettings"` + } `json:"thread"` + Space struct { + Name string `json:"name"` + Type string `json:"type"` + } `json:"space"` + ArgumentText string `json:"argumentText"` + } `json:"message"` + User struct { + Name string `json:"name"` + DisplayName string `json:"displayName"` + AvatarURL string `json:"avatarUrl"` + Email string `json:"email"` + Type string `json:"type"` + } `json:"user"` + Space struct { + Name string `json:"name"` + Type string `json:"type"` + DisplayName string `json:"displayName"` + } `json:"space"` + ConfigCompleteRedirectURL string `json:"configCompleteRedirectUrl"` +} + +// ReplyThread is a part of reply messages +type ReplyThread struct { + Name string `json:"name,omitempty"` +} + +// ReplyMessage is partial hangouts format of messages used +// For details see +// https://developers.google.com/hangouts/chat/reference/rest/v1/spaces.messages#Message +type ReplyMessage struct { + Text string `json:"text"` + Thread *ReplyThread `json:"thread,omitempty"` +} diff --git a/google-chat/google-chat.go b/google-chat/google-chat.go new file mode 100644 index 0000000..8ceed5f --- /dev/null +++ b/google-chat/google-chat.go @@ -0,0 +1,163 @@ +package googlechat + +import ( + "bytes" + "cloud.google.com/go/pubsub" + "context" + "encoding/json" + "github.com/go-chat-bot/bot" + "golang.org/x/oauth2/google" + "log" + "net/http" + "strings" + "time" +) + +const ( + chatAuthScope = "https://www.googleapis.com/auth/chat.bot" + apiEndpoint = "https://chat.googleapis.com/v1/" +) + +var ( + httpChatClient *http.Client + b *bot.Bot +) + +// Config must contain basic configuration for the bot to be able to work +type Config struct { + PubSubProject string + TopicName string + SubscriptionName string + Token string + WelcomeMessage string +} + +func responseHandler(target string, message string, sender *bot.User) { + var space, thread string + + // this define thread in the reply if we can so we don't alwayus start new + targets := strings.Split(target, ":") + if len(targets) < 2 { + space = target + } else { + space = targets[0] + thread = targets[1] + } + + reply, err := json.Marshal(&ReplyMessage{ + Text: message, + Thread: &ReplyThread{ + Name: thread}}) + + log.Printf("Replying: space: %s thread: %s message: %s\n", + space, thread, message) + resp, err := httpChatClient.Post(apiEndpoint+space+"/messages", + "application/json", + bytes.NewReader(reply)) + if err != nil { + log.Printf("Error posting reply: %v", err) + } + + log.Printf("Response: %s\n", resp.Status) +} + +// Run reads the config, establishes OAuth connection & Pub/Sub subscription to +// the message queue +func Run(config *Config) { + var err error + ctx := context.Background() + httpChatClient, err = google.DefaultClient(ctx, chatAuthScope) + if err != nil { + log.Printf("Error setting http client: %v\n", err) + return + } + + client, err := pubsub.NewClient(ctx, config.PubSubProject) + if err != nil { + log.Printf("Error creating client: %v\n", err) + return + } + + topic := client.Topic(config.TopicName) + + // Create a new subscription to the previously created topic + // with the given name. + sub := client.Subscription(config.SubscriptionName) + ok, err := sub.Exists(ctx) + if err != nil { + log.Printf("Error getting subscription: %v\n", err) + return + } + if !ok { + // Subscription doesn't exist. + sub, err = client.CreateSubscription(ctx, config.SubscriptionName, + pubsub.SubscriptionConfig{ + Topic: topic, + AckDeadline: 10 * time.Second, + }) + if err != nil { + log.Printf("Error subscribing: %v\n", err) + return + } + } + + b = bot.New(&bot.Handlers{ + Response: responseHandler, + }) + + err = sub.Receive(context.Background(), + func(ctx context.Context, m *pubsub.Message) { + var msg ChatMessage + err = json.Unmarshal(m.Data, &msg) + if err != nil { + log.Printf("Failed message unmarshal(%v): %s\n", err, m.Data) + m.Ack() + return + } + if msg.Token != config.Token { + log.Printf("Failed to verify token: %s", msg.Token) + m.Ack() + return + } + + log.Printf("Space: %s (%s)\n", msg.Space.Name, msg.Space.DisplayName) + log.Printf("Message type: %s\n", msg.Type) + log.Printf("From: %s (%s)\n", msg.User.Name, msg.User.DisplayName) + switch msg.Type { + case "ADDED_TO_SPACE": + if config.WelcomeMessage != "" { + log.Printf("Sending welcome message to %s\n", msg.Space.Name) + b.SendMessage(msg.Space.Name, config.WelcomeMessage, nil) + } + case "REMOVED_FROM_SPACE": + break + case "MESSAGE": + log.Printf("Message: %s\n", msg.Message.ArgumentText) + b.MessageReceived( + &bot.ChannelData{ + Protocol: "googlechat", + Server: "chat.google.com", + HumanName: msg.Space.DisplayName, + Channel: msg.Space.Name + ":" + msg.Message.Thread.Name, + IsPrivate: msg.Space.Type == "DM", + }, + &bot.Message{ + Text: msg.Message.ArgumentText, + IsAction: false, + }, + &bot.User{ + ID: msg.User.Name, + Nick: msg.User.DisplayName, + RealName: msg.User.DisplayName, + }) + } + + m.Ack() + }) + if err != nil { + log.Printf("Error setting up receiving: %v\n", err) + return + } + // Wait indefinetely + select {} +} From 3e6edb86f5c95cffb7a8fa6767f4ec3b04613ad2 Mon Sep 17 00:00:00 2001 From: Benjamin Foote Date: Thu, 10 Jan 2019 14:42:57 -0800 Subject: [PATCH 23/42] Message streams to recieve events from outside of the bot and send to any channel (#101) --- bot.go | 55 +++++++++++++++++++++- cmd.go | 93 ++++++++++++++++++++++++++++++++++++++ cmd_test.go | 87 +++++++++++++++++++++++++++++++++-- debug/main.go | 7 ++- google-chat/google-chat.go | 20 +++++--- irc/irc.go | 13 ++++-- rocket/rocket.go | 14 +++++- slack/slack.go | 9 +++- telegram/telegram.go | 16 +++++-- 9 files changed, 293 insertions(+), 21 deletions(-) diff --git a/bot.go b/bot.go index 7d632f4..3bd3880 100644 --- a/bot.go +++ b/bot.go @@ -27,6 +27,29 @@ type Bot struct { disabledCmds []string msgsToSend chan responseMessage done chan struct{} + + // Protocol and Server are used by MesssageStreams to + // determine if this is the correct bot to send a message on + // see: + // https://github.com/go-chat-bot/bot/issues/37#issuecomment-277661159 + // https://github.com/go-chat-bot/bot/issues/97#issuecomment-442827599 + Protocol string + // Server and Protocol are used by MesssageStreams to + // determine if this is the correct bot to send a message on + // see: + // https://github.com/go-chat-bot/bot/issues/37#issuecomment-277661159 + // https://github.com/go-chat-bot/bot/issues/97#issuecomment-442827599 + Server string +} + +// Config configuration for this Bot instance +type Config struct { + // Protocol and Server are used by MesssageStreams to + /// determine if this is the correct bot to send a message on + Protocol string + // Server and Protocol are used by MesssageStreams to + // determine if this is the correct bot to send a message on + Server string } type responseMessage struct { @@ -51,7 +74,7 @@ func logErrorHandler(msg string, err error) { } // New configures a new bot instance -func New(h *Handlers) *Bot { +func New(h *Handlers, bc *Config) *Bot { if h.Errored == nil { h.Errored = logErrorHandler } @@ -61,16 +84,46 @@ func New(h *Handlers) *Bot { cron: cron.New(), msgsToSend: make(chan responseMessage, MsgBuffer), done: make(chan struct{}), + Protocol: bc.Protocol, + Server: bc.Server, } // Launch the background goroutine that isolates the possibly non-threadsafe // message sending logic of the underlying transport layer. go b.processMessages() + b.startMessageStreams() + b.startPeriodicCommands() return b } +func (b *Bot) startMessageStreams() { + for _, v := range messageStreamConfigs { + + go func(b *Bot, config *messageStreamConfig) { + msMap.Lock() + ms := &MessageStream{ + Data: make(chan MessageStreamMessage), + Done: make(chan bool), + } + var err = config.msgFunc(ms) + if err != nil { + b.errored("MessageStream "+config.streamName+" failed ", err) + } + msKey := messageStreamKey{ + Protocol: b.Protocol, + Server: b.Server, + StreamName: config.streamName, + } + // thread safe write + msMap.messageStreams[msKey] = ms + msMap.Unlock() + b.handleMessageStream(config.streamName, ms) + }(b, v) + } +} + func (b *Bot) startPeriodicCommands() { for _, config := range periodicCommands { func(b *Bot, config PeriodicConfig) { diff --git a/cmd.go b/cmd.go index ed522dd..5ee5492 100644 --- a/cmd.go +++ b/cmd.go @@ -6,6 +6,12 @@ import ( "sync" ) +var ( + // ErrProtocolServerMismatch server && proto must match + ErrProtocolServerMismatch = errors.New("the specified protocol and server do not correspond to this bot instance") + errNoChannelSpecified = errors.New("no channel was specified for this message") +) + // Cmd holds the parsed user's input for easier handling of commands type Cmd struct { Raw string // Raw is full string passed to the command @@ -73,6 +79,20 @@ type User struct { IsBot bool } +// MessageStream allows event information to be transmitted to an arbitrary channel +// https://github.com/go-chat-bot/bot/issues/97 +type MessageStream struct { + Data chan MessageStreamMessage + // Done is almost never called, usually the bot should just leave the chan open + Done chan bool +} + +// MessageStreamMessage the actual Message passed back to MessageStream in a chan +type MessageStreamMessage struct { + Message string + ChannelData *ChannelData +} + type customCommand struct { Version int Cmd string @@ -123,11 +143,36 @@ type activeCmdFuncV3 func(cmd *Cmd) (CmdResultV3, error) type filterCmdFuncV1 func(cmd *FilterCmd) (string, error) +type messageStreamFunc func(ms *MessageStream) error + +type messageStreamSyncMap struct { + sync.RWMutex + messageStreams map[messageStreamKey]*MessageStream +} +type messageStreamKey struct { + StreamName string + Server string + Protocol string +} + +// messageStreamConfig holds the registered function for the streamname +type messageStreamConfig struct { + version int + streamName string + msgFunc messageStreamFunc +} + var ( commands = make(map[string]*customCommand) passiveCommands = make(map[string]*customCommand) filterCommands = make(map[string]*customCommand) periodicCommands = make(map[string]PeriodicConfig) + + messageStreamConfigs []*messageStreamConfig + + msMap = &messageStreamSyncMap{ + messageStreams: make(map[messageStreamKey]*MessageStream), + } ) // RegisterCommand adds a new command to the bot. @@ -170,6 +215,19 @@ func RegisterCommandV3(command, description, exampleArgs string, cmdFunc activeC } } +// RegisterMessageStream adds a new message stream to the bot. +// The command should be registered in the Init() func of your package +// MessageStreams send messages to a channel +// streamName: String used to identify the command, for internal use only (ex: webhook) +// messageStreamFunc: Function which will be executed. It will received a MessageStream with a chan to push +func RegisterMessageStream(streamName string, msgFunc messageStreamFunc) { + messageStreamConfigs = append(messageStreamConfigs, &messageStreamConfig{ + version: v1, + streamName: streamName, + msgFunc: msgFunc, + }) +} + // RegisterPassiveCommand adds a new passive command to the bot. // The command should be registered in the Init() func of your package // Passive commands receives all the text posted to a channel without any parsing @@ -266,6 +324,7 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { b.errored(fmt.Sprintf("Error executing %s", cmdFunc.Cmd), err) return } + for { select { case message := <-result.Message: @@ -358,3 +417,37 @@ func (b *Bot) checkCmdError(err error, c *Cmd) { b.SendMessage(c.Channel, errorMsg, c.User) } } + +// handleMessageStream +// if there are two bots (telegram, irc) and three messsages(a, b, c) then there will be six entries in messageStreams[key] +// when a message is sent into a chan it has a good chance of arriving at the wrong bot instance +// for every message we check to see if it matched this b.Protocol and b.Server +// if it doesn't we lookup the entry in messageStreams[key] send it to *that* Data chan +func (b *Bot) handleMessageStream(streamName string, ms *MessageStream) { + for { + select { + case d := <-ms.Data: + + if d.ChannelData.Protocol != b.Protocol || d.ChannelData.Server != b.Server { + // then lookup who it *should* be sent to and send it back into *that* chan + key := messageStreamKey{Protocol: d.ChannelData.Protocol, Server: d.ChannelData.Server, StreamName: streamName} + msMap.RLock() + msMap.messageStreams[key].Data <- d + msMap.RUnlock() + continue + } + + // this message is meant for us! + + if d.ChannelData.Channel == "" { + b.errored("handleMessageStream: "+d.Message, errNoChannelSpecified) + continue + } + if d.Message != "" { + b.SendMessage(d.ChannelData.Channel, d.Message, nil) + } + case <-ms.Done: + return + } + } +} diff --git a/cmd_test.go b/cmd_test.go index 98c4f24..6eb5772 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -6,6 +6,7 @@ import ( "reflect" "sort" "strings" + "sync" "testing" "time" ) @@ -72,7 +73,12 @@ func newBot() *Bot { return New(&Handlers{ Response: responseHandler, Errored: errorHandler, - }) + }, + &Config{ + Protocol: "test", + Server: "test", + }, + ) } func registerValidCommand() { @@ -90,7 +96,10 @@ func TestPeriodicCommands(t *testing.T) { Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "ok " + channel, nil }, }) - b := New(&Handlers{Response: responseHandler}) + b := New( + &Handlers{Response: responseHandler}, + &Config{Protocol: "test", Server: "test"}, + ) defer b.Close() entries := b.cron.Entries() @@ -123,7 +132,10 @@ func TestMultiplePeriodicCommands(t *testing.T) { Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "ok_afternoon " + channel, nil }, }) - b := New(&Handlers{Response: responseHandler}) + b := New( + &Handlers{Response: responseHandler}, + &Config{Protocol: "test", Server: "test"}, + ) defer b.Close() entries := b.cron.Entries() @@ -200,7 +212,9 @@ func TestPeriodicCommandsV2(t *testing.T) { channel = target user = sender replies <- message - }}) + }}, + &Config{Protocol: "test", Server: "test"}) + defer b.Close() entries := b.cron.Entries() @@ -672,3 +686,68 @@ func TestFilterCommandSilence(t *testing.T) { t.Error("Expected 1 error") } } + +// how to test channels.. +// https://www.hugopicado.com/2016/10/01/testing-over-golang-channels.html + +func TestMessageStreams(t *testing.T) { + var mutex = &sync.Mutex{} + reset() + + var msSender1 *MessageStream + var msSender2 *MessageStream + + RegisterMessageStream("streamOne", func(ms1 *MessageStream) error { + mutex.Lock() + msSender1 = ms1 + mutex.Unlock() + return nil + }) + RegisterMessageStream("streamTwo", func(ms2 *MessageStream) error { + mutex.Lock() + msSender2 = ms2 + mutex.Unlock() + return nil + }) + + b1 := New(&Handlers{Response: responseHandler, Errored: errorHandler}, &Config{Protocol: "protoA", Server: "test"}) + b2 := New(&Handlers{Response: responseHandler, Errored: errorHandler}, &Config{Protocol: "protoB", Server: "test"}) + + msmB1 := MessageStreamMessage{ + Message: "hello botOne", + ChannelData: &ChannelData{Server: b1.Server, Protocol: b1.Protocol, Channel: "#go-bot"}, + } + msmB2 := MessageStreamMessage{ + Message: "hello botTwo", + ChannelData: &ChannelData{Server: b2.Server, Protocol: b2.Protocol, Channel: "#go-bot"}, + } + + // give New() a second to make() the chans and setup the objects + time.Sleep(2 * time.Second) + + // when you send a message destined for b1 #go-bot, even if you send it to b2, it should arrive at b1 + mutex.Lock() + msSender1.Data <- msmB1 + if "hello botOne" != <-replies { + t.Fatal("message not Recieved at Channel") + } + + msSender2.Data <- msmB1 + if "hello botOne" != <-replies { + t.Fatal("message not Recieved at Channel") + } + + // and vice-versa + // when you send a message destined for b2 #go-bots, even if you send it to b1, it should arrive at b2 + msSender1.Data <- msmB2 + if "hello botTwo" != <-replies { + t.Fatal("message not Recieved at Channel") + } + + msSender2.Data <- msmB2 + if "hello botTwo" != <-replies { + t.Fatal("message not Recieved at Channel") + } + mutex.Unlock() + +} diff --git a/debug/main.go b/debug/main.go index 53ab159..169145b 100644 --- a/debug/main.go +++ b/debug/main.go @@ -41,7 +41,12 @@ func responseHandler(target string, message string, sender *bot.User) { func main() { b := bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, + &bot.Config{ + Protocol: "debug", + Server: "debug", + }, + ) fmt.Println("Type a command or !help for available commands...") diff --git a/google-chat/google-chat.go b/google-chat/google-chat.go index 8ceed5f..dc91b01 100644 --- a/google-chat/google-chat.go +++ b/google-chat/google-chat.go @@ -2,20 +2,23 @@ package googlechat import ( "bytes" - "cloud.google.com/go/pubsub" "context" "encoding/json" - "github.com/go-chat-bot/bot" - "golang.org/x/oauth2/google" "log" "net/http" "strings" "time" + + "cloud.google.com/go/pubsub" + "github.com/go-chat-bot/bot" + "golang.org/x/oauth2/google" ) const ( chatAuthScope = "https://www.googleapis.com/auth/chat.bot" apiEndpoint = "https://chat.googleapis.com/v1/" + protocol = "googlechat" + server = "chat.google.com" ) var ( @@ -103,7 +106,12 @@ func Run(config *Config) { b = bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, + &bot.Config{ + Protocol: protocol, + Server: server, + }, + ) err = sub.Receive(context.Background(), func(ctx context.Context, m *pubsub.Message) { @@ -135,8 +143,8 @@ func Run(config *Config) { log.Printf("Message: %s\n", msg.Message.ArgumentText) b.MessageReceived( &bot.ChannelData{ - Protocol: "googlechat", - Server: "chat.google.com", + Protocol: protocol, + Server: server, HumanName: msg.Space.DisplayName, Channel: msg.Space.Name + ":" + msg.Message.Thread.Name, IsPrivate: msg.Space.Type == "DM", diff --git a/irc/irc.go b/irc/irc.go index 7e361ca..907db4a 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -29,6 +29,8 @@ var ( b *bot.Bot ) +const protocol = "irc" + func responseHandler(target string, message string, sender *bot.User) { channel := target if ircConn.GetNick() == target { @@ -45,7 +47,7 @@ func responseHandler(target string, message string, sender *bot.User) { func onPRIVMSG(e *ircevent.Event) { b.MessageReceived( &bot.ChannelData{ - Protocol: "irc", + Protocol: protocol, Server: ircConn.Server, Channel: e.Arguments[0], IsPrivate: e.Arguments[0] == ircConn.GetNick()}, @@ -61,7 +63,7 @@ func onPRIVMSG(e *ircevent.Event) { func onCTCPACTION(e *ircevent.Event) { b.MessageReceived( &bot.ChannelData{ - Protocol: "irc", + Protocol: protocol, Server: ircConn.Server, Channel: e.Arguments[0], IsPrivate: false, @@ -108,7 +110,12 @@ func SetUp(c *Config) *bot.Bot { b = bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, + &bot.Config{ + Protocol: protocol, + Server: c.Server, + }, + ) ircConn.AddCallback("001", onWelcome) ircConn.AddCallback("PRIVMSG", onPRIVMSG) diff --git a/rocket/rocket.go b/rocket/rocket.go index ea9f5ec..6d37f39 100644 --- a/rocket/rocket.go +++ b/rocket/rocket.go @@ -14,6 +14,10 @@ var ( config *Config ) +const ( + protocol = "rocket" +) + // Config must contain the necessary data to connect to an rocket.chat server type Config struct { Server string @@ -55,7 +59,13 @@ func Run(c *Config) { b := bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, + &bot.Config{ + Protocol: protocol, + Server: config.Server, + }, + ) + b.Disable([]string{"url"}) msgChan := client.GetAllMessages() @@ -66,7 +76,7 @@ func Run(c *Config) { if !ownMessage(c, msg) { b.MessageReceived( &bot.ChannelData{ - Protocol: "rocket", + Protocol: protocol, Server: "", Channel: msg.ChannelId, IsPrivate: false, diff --git a/slack/slack.go b/slack/slack.go index 92ab4d3..3f6af0d 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -23,6 +23,8 @@ var ( botUserID = "" ) +const protocol = "slack" + func defaultMessageFilter(message string, _ *bot.User) (string, slack.PostMessageParameters) { return message, params } @@ -125,7 +127,12 @@ func Run(token string) { b := bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, + &bot.Config{ + Protocol: protocol, + Server: teaminfo.Domain, + }, + ) b.Disable([]string{"url"}) diff --git a/telegram/telegram.go b/telegram/telegram.go index 0337913..9688d19 100644 --- a/telegram/telegram.go +++ b/telegram/telegram.go @@ -14,6 +14,11 @@ var ( tg *tgbotapi.BotAPI ) +const ( + protocol = "telegram" + server = "telegram" +) + func responseHandler(target string, message string, sender *bot.User) { id, err := strconv.ParseInt(target, 10, 64) if err != nil { @@ -47,13 +52,18 @@ func Run(token string, debug bool) { b := bot.New(&bot.Handlers{ Response: responseHandler, - }) + }, &bot.Config{ + Protocol: protocol, + Server: server, + }, + ) + b.Disable([]string{"url"}) for update := range updates { target := &bot.ChannelData{ - Protocol: "telegram", - Server: "telegram", + Protocol: protocol, + Server: server, Channel: strconv.FormatInt(update.Message.Chat.ID, 10), IsPrivate: update.Message.Chat.IsPrivate()} name := []string{update.Message.From.FirstName, update.Message.From.LastName} From ece33b98f74209dd00d4915be15c11e14ca8aa67 Mon Sep 17 00:00:00 2001 From: Odd Eivind Ebbesen Date: Thu, 7 Feb 2019 18:14:53 +0100 Subject: [PATCH 24/42] Added func SetUpConn that wraps SetUp and returns ircConn in addtion to bot (#102) --- irc/irc.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/irc/irc.go b/irc/irc.go index 907db4a..01e56a7 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -124,6 +124,11 @@ func SetUp(c *Config) *bot.Bot { return b } +// SetUpConn wraps SetUp and returns ircConn in addition to bot +func SetUpConn(c *Config) (*bot.Bot, *ircevent.Connection) { + return SetUp(c), ircConn +} + // Run reads the Config, connect to the specified IRC server and starts the bot. // The bot will automatically join all the channels specified in the configuration func Run(c *Config) { From d2e4b4ae907d344078d81f909231ce10a91e827f Mon Sep 17 00:00:00 2001 From: Odd Eivind Ebbesen Date: Tue, 19 Feb 2019 12:36:48 +0100 Subject: [PATCH 25/42] Removed usage of Unidecode on argument parsing (#105) --- parser.go | 2 +- parser_test.go | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/parser.go b/parser.go index 081b1ca..883d372 100644 --- a/parser.go +++ b/parser.go @@ -41,7 +41,7 @@ func parse(s string, channel *ChannelData, user *User) (*Cmd, error) { if len(pieces) > 1 { // get the arguments and remove extra spaces c.RawArgs = strings.TrimSpace(pieces[1]) - parsedArgs, err := shellwords.Parse(unidecode.Unidecode(c.RawArgs)) + parsedArgs, err := shellwords.Parse(c.RawArgs) if err != nil { return nil, errors.New("Error parsing arguments: " + err.Error()) } diff --git a/parser_test.go b/parser_test.go index c7b4f99..2bd5b27 100644 --- a/parser_test.go +++ b/parser_test.go @@ -15,7 +15,6 @@ func TestParser(t *testing.T) { cmdMixedCaseWithArgs := CmdPrefix + "Cmd arg1 arg2 " cmdMixedCaseWithQuotes := CmdPrefix + "Cmd \"arg1 arg2\"" cmdUnicode := CmdPrefix + "Çmd" - cmdUnicodeWithArgs := CmdPrefix + "çмд Ã¥rg1 Ã¥rg2" tests := []struct { msg string @@ -95,17 +94,6 @@ func TestParser(t *testing.T) { Message: "Çmd", MessageData: &Message{Text: strings.TrimLeft("Çmd", CmdPrefix)}, }}, - {cmdUnicodeWithArgs, &Cmd{ - Raw: cmdUnicodeWithArgs, - Command: "cmd", - Channel: channel.Channel, - ChannelData: channel, - User: user, - Message: "çмд Ã¥rg1 Ã¥rg2", - RawArgs: "Ã¥rg1 Ã¥rg2", - Args: []string{"arg1", "arg2"}, - MessageData: &Message{Text: strings.TrimLeft("çмд Ã¥rg1 Ã¥rg2", CmdPrefix)}, - }}, } for _, test := range tests { From e13ffd51259d5d03b94a6e6f1fb61f25390397c1 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 29 Mar 2019 13:06:12 +0100 Subject: [PATCH 26/42] Fix slack api (#108) --- slack/slack.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slack/slack.go b/slack/slack.go index 3f6af0d..733068c 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -31,7 +31,11 @@ func defaultMessageFilter(message string, _ *bot.User) (string, slack.PostMessag func responseHandler(target string, message string, sender *bot.User) { message, params := messageFilter(message, sender) - api.PostMessage(target, message, params) + _, _, err := api.PostMessage(target, slack.MsgOptionPostMessageParameters(params), + slack.MsgOptionText(message, false)) + if err != nil { + fmt.Printf("Error sending a slack message: %s\n", err.Error()) + } } // FindUserBySlackID converts a slack.User into a bot.User struct From a72276ecd4eadb8f0129aa19caad1fc180d8f0f7 Mon Sep 17 00:00:00 2001 From: Gilgamesh <32211128+Dontmindmes@users.noreply.github.com> Date: Thu, 4 Apr 2019 05:08:50 -0700 Subject: [PATCH 27/42] Fixed repository URL on README (#109) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14c2268..f2150eb 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This project uses the new [Go 1.11 modules](https://github.com/golang/go/wiki/Mo To test the bot, use the [debug](https://github.com/go-chat-bot/bot/tree/master/debug) console app. -- Clone this repository or use `go get github.com/go-chat-bot` +- Clone this repository or use `go get github.com/go-chat-bot/bot` - Build everything: `go build ./...` - Build and execute the debug app: - `cd debug` From 34d7e9b3b1509d267b862e3238dd9fa7e5be4581 Mon Sep 17 00:00:00 2001 From: teddy Date: Thu, 25 Jul 2019 07:31:34 -0700 Subject: [PATCH 28/42] Update Slack Version Dependency (#111) After we fixed the slack plugin to send in the right format, we failed to update the module dependency to account for that. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4952a3a..69fec9d 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect github.com/mattn/go-shellwords v1.0.3 github.com/mozillazg/go-unidecode v0.1.0 - github.com/nlopes/slack v0.4.0 + github.com/nlopes/slack v0.5.0 github.com/pkg/errors v0.8.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 From 9b89c724b743da76eab1b98a2ff3f43f07bd6878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Fri, 13 Sep 2019 09:25:59 -0300 Subject: [PATCH 29/42] Updates robfig/cron to v3 (#114) I saw some discussions about this, but I think that its best to fix this library version and use standard cron syntax. * Updates go to 1.13 on circle ci --- .circleci/config.yml | 4 +--- bot.go | 2 +- cmd_test.go | 12 ++++++------ go.mod | 4 +++- go.sum | 5 +++++ 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a16af01..42688e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,9 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/golang:1.11 - environment: - GO111MODULE: "on" + - image: circleci/golang:1.13 working_directory: /go/src/github.com/go-chat-bot/bot steps: - checkout diff --git a/bot.go b/bot.go index 3bd3880..1bc4a93 100644 --- a/bot.go +++ b/bot.go @@ -7,7 +7,7 @@ import ( "math/rand" "time" - "github.com/robfig/cron" + "github.com/robfig/cron/v3" ) const ( diff --git a/cmd_test.go b/cmd_test.go index 6eb5772..8317aab 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -92,7 +92,7 @@ func TestPeriodicCommands(t *testing.T) { reset() RegisterPeriodicCommand("morning", PeriodicConfig{ - CronSpec: "0 0 08 * * mon-fri", + CronSpec: "0 08 * * mon-fri", Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "ok " + channel, nil }, }) @@ -122,13 +122,13 @@ func TestMultiplePeriodicCommands(t *testing.T) { reset() RegisterPeriodicCommand("morning", PeriodicConfig{ - CronSpec: "0 0 08 * * mon-fri", + CronSpec: "0 08 * * mon-fri", Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "ok_morning " + channel, nil }, }) RegisterPeriodicCommand("afternoon", PeriodicConfig{ - CronSpec: "0 0 12 * * mon-fri", + CronSpec: "0 12 * * mon-fri", Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "ok_afternoon " + channel, nil }, }) @@ -170,7 +170,7 @@ func TestErroredPeriodicCommand(t *testing.T) { reset() RegisterPeriodicCommand("bugged", PeriodicConfig{ - CronSpec: "0 0 08 * * mon-fri", + CronSpec: "0 08 * * mon-fri", Channels: []string{"#channel"}, CmdFunc: func(channel string) (string, error) { return "bug", errors.New("error") }, }) @@ -198,7 +198,7 @@ func TestPeriodicCommandsV2(t *testing.T) { reset() RegisterPeriodicCommandV2("morning", PeriodicConfig{ - CronSpec: "0 0 08 * * mon-fri", + CronSpec: "0 08 * * mon-fri", CmdFuncV2: func() ([]CmdResult, error) { ret := []CmdResult{ {Message: "message 1", Channel: "#channel1"}, @@ -253,7 +253,7 @@ func TestErroredPeriodicCommandsV2(t *testing.T) { reset() RegisterPeriodicCommandV2("morning", PeriodicConfig{ - CronSpec: "0 0 08 * * mon-fri", + CronSpec: "0 08 * * mon-fri", CmdFuncV2: func() ([]CmdResult, error) { return nil, errors.New("error") }, diff --git a/go.mod b/go.mod index 69fec9d..f7f3b97 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nlopes/slack v0.5.0 github.com/pkg/errors v0.8.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f - github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 + github.com/robfig/cron/v3 v3.0.0 github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 github.com/trivago/tgo v1.0.5 // indirect @@ -30,3 +30,5 @@ require ( google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect gopkg.in/telegram-bot-api.v3 v3.0.0 ) + +go 1.13 diff --git a/go.sum b/go.sum index 228b440..d9dcb35 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/mozillazg/go-unidecode v0.1.0 h1:wAIMDf/yTexXKxT5TkctLwmClGSyuoJaZDRM github.com/mozillazg/go-unidecode v0.1.0/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA= github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= +github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -51,6 +53,9 @@ github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaN github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= +github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQMTscCQ7VH3JAVuxb/pe53v2LBiA7z8= From 4dad28f1bc914bb9ab5232fb1761d386ede5030a Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 13 Sep 2019 05:27:30 -0700 Subject: [PATCH 30/42] Allow Protocol Argument Passing (#112) Due to the generic nature of this framework, platform specific behaviors are hard to take advantage of. With this PR, protocol plugins can offer additional information for message handlers to read, and accept additional information as well. In particular, this change was tested to allow a Slack response to be made as a threaded reply, instead of only ever in the main channel. EG: func threadedEcho(cmd *bot.Cmd) (bot.CmdResult, error) { var params *slack.PostMessageParameters if ev, ok := cmd.MessageData.ProtoMsg.(*slack.MessageEvent); ok { params = &slack.PostMessageParameters{ AsUser: true, ThreadTimestamp: ev.Timestamp, } } return bot.CmdResult{ Message: fmt.Sprintf("%s: %s", cmd.User.Nick, cmd.Raw), Channel: cmd.Channel, ProtoParams: params, }, nil } --- bot.go | 55 ++++++++++++++++++++++++++----- cmd.go | 31 +++++++++++++----- cmd_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++---- help.go | 5 ++- parser.go | 24 +++++++------- parser_test.go | 8 +++-- slack/slack.go | 65 ++++++++++++++++++++++++------------ 7 files changed, 216 insertions(+), 61 deletions(-) diff --git a/bot.go b/bot.go index 1bc4a93..76c8c10 100644 --- a/bot.go +++ b/bot.go @@ -55,18 +55,31 @@ type Config struct { type responseMessage struct { target, message string sender *User + protoParams interface{} +} + +// OutgoingMessage collects all the information for a message to go out. +type OutgoingMessage struct { + Target string + Message string + Sender *User + ProtoParams interface{} } // ResponseHandler must be implemented by the protocol to handle the bot responses type ResponseHandler func(target, message string, sender *User) +// ResponseHandlerV2 may be implemented by the protocol to handle the bot responses +type ResponseHandlerV2 func(OutgoingMessage) + // ErrorHandler will be called when an error happens type ErrorHandler func(msg string, err error) // Handlers that must be registered to receive callbacks from the bot type Handlers struct { - Response ResponseHandler - Errored ErrorHandler + Response ResponseHandler + ResponseV2 ResponseHandlerV2 + Errored ErrorHandler } func logErrorHandler(msg string, err error) { @@ -158,7 +171,7 @@ func (b *Bot) startPeriodicCommands() { // MessageReceived must be called by the protocol upon receiving a message func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *User) { - command, err := parse(message.Text, channel, sender) + command, err := parse(message, channel, sender) if err != nil { b.SendMessage(channel.Channel, err.Error(), sender) return @@ -189,23 +202,47 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us // SendMessage queues a message for a target recipient, optionally from a particular sender. func (b *Bot) SendMessage(target string, message string, sender *User) { - message = b.executeFilterCommands(&FilterCmd{ + b.SendMessageV2(OutgoingMessage{ Target: target, Message: message, - User: sender}) + Sender: sender, + }) +} + +// SendMessageV2 queues a message. +func (b *Bot) SendMessageV2(om OutgoingMessage) { + message := b.executeFilterCommands(&FilterCmd{ + Target: om.Target, + Message: om.Message, + User: om.Sender, + }) if message == "" { return } select { - case b.msgsToSend <- responseMessage{target, message, sender}: + case b.msgsToSend <- responseMessage{ + target: om.Target, + message: message, + sender: om.Sender, + protoParams: om.ProtoParams, + }: default: b.errored("Failed to queue message to send.", errors.New("Too busy")) } } -func (b *Bot) sendResponse(target, message string, sender *User) { - b.handlers.Response(target, message, sender) +func (b *Bot) sendResponse(resp responseMessage) { + if b.handlers.ResponseV2 != nil { + b.handlers.ResponseV2(OutgoingMessage{ + Message: resp.message, + ProtoParams: resp.protoParams, + Sender: resp.sender, + Target: resp.target, + }) + return + } + b.handlers.Response(resp.target, resp.message, resp.sender) } func (b *Bot) errored(msg string, err error) { @@ -218,7 +255,7 @@ func (b *Bot) processMessages() { for { select { case msg := <-b.msgsToSend: - b.sendResponse(msg.target, msg.message, msg.sender) + b.sendResponse(msg) case <-b.done: return } diff --git a/cmd.go b/cmd.go index 5ee5492..2d754fa 100644 --- a/cmd.go +++ b/cmd.go @@ -41,8 +41,9 @@ func (c *ChannelData) URI() string { // Message holds the message info - for IRC and Slack networks, this can include whether the message was an action. type Message struct { - Text string // The actual content of this Message - IsAction bool // True if this was a '/me does something' message + Text string // The actual content of this Message + IsAction bool // True if this was a '/me does something' message + ProtoMsg interface{} // The underlying object that we got from the protocol pkg } // FilterCmd holds information about what is output being filtered - message and @@ -108,15 +109,17 @@ type customCommand struct { // CmdResult is the result message of V2 commands type CmdResult struct { - Channel string // The channel where the bot should send the message - Message string // The message to be sent + Channel string // The channel where the bot should send the message + Message string // The message to be sent + ProtoParams interface{} } // CmdResultV3 is the result message of V3 commands type CmdResultV3 struct { - Channel string - Message chan string - Done chan bool + Channel string + Message chan string + Done chan bool + ProtoParams interface{} } const ( @@ -389,7 +392,12 @@ func (b *Bot) handleCmd(c *Cmd) { } if result.Message != "" { - b.SendMessage(result.Channel, result.Message, c.User) + b.SendMessageV2(OutgoingMessage{ + Target: result.Channel, + Message: result.Message, + Sender: c.User, + ProtoParams: result.ProtoParams, + }) } case v3: result, err := cmd.CmdFuncV3(c) @@ -401,7 +409,12 @@ func (b *Bot) handleCmd(c *Cmd) { select { case message := <-result.Message: if message != "" { - b.SendMessage(result.Channel, message, c.User) + b.SendMessageV2(OutgoingMessage{ + Target: result.Channel, + Message: message, + Sender: c.User, + ProtoParams: result.ProtoParams, + }) } case <-result.Done: return diff --git a/cmd_test.go b/cmd_test.go index 8317aab..99ae550 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -12,12 +12,13 @@ import ( ) var ( - channel string - replies chan string - cmdError chan string - user *User - msgs []string - errs []string + channel string + replies chan string + cmdError chan string + user *User + msgs []string + errs []string + protoParams interface{} ) const ( @@ -52,6 +53,13 @@ func responseHandler(target string, message string, sender *User) { replies <- message } +func responseHandlerV2(om OutgoingMessage) { + channel = om.Target + user = om.Sender + protoParams = om.ProtoParams + replies <- om.Message +} + func errorHandler(msg string, err error) { cmdError <- fmt.Sprintf("%s: %s", msg, err) } @@ -63,6 +71,7 @@ func reset() { cmdError = make(chan string, 10) msgs = []string{} errs = []string{} + protoParams = nil commands = make(map[string]*customCommand) periodicCommands = make(map[string]PeriodicConfig) passiveCommands = make(map[string]*customCommand) @@ -81,6 +90,19 @@ func newBot() *Bot { ) } +func newBotV2() *Bot { + return New(&Handlers{ + Response: responseHandler, + ResponseV2: responseHandlerV2, + Errored: errorHandler, + }, + &Config{ + Protocol: "test", + Server: "test", + }, + ) +} + func registerValidCommand() { RegisterCommand(cmd, cmdDescription, cmdExampleArgs, func(c *Cmd) (string, error) { @@ -496,6 +518,37 @@ func TestCmdV2(t *testing.T) { } } +func TestCmdV2WithProtoParams(t *testing.T) { + reset() + RegisterCommandV2("cmd", "", "", + func(c *Cmd) (CmdResult, error) { + return CmdResult{ + Channel: "#channel", + Message: "message", + ProtoParams: &CmdResult{Message: "Nested!"}, + }, nil + }) + + b := newBotV2() + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "!cmd"}, &User{Nick: "user"}) + + waitMessages(t, 1, 0) + + if channel != "#channel" { + t.Error("Wrong channel") + } + if !reflect.DeepEqual([]string{"message"}, msgs) { + t.Error("Invalid reply") + } + if pa, ok := protoParams.(*CmdResult); ok { + if pa.Message != "Nested!" { + t.Error("Information lost in copying.") + } + } else { + t.Error("Failed to pass proto args through.") + } +} + func TestCmdV2WithoutSpecifyingChannel(t *testing.T) { reset() RegisterCommandV2("cmd", "", "", @@ -687,6 +740,30 @@ func TestFilterCommandSilence(t *testing.T) { } } +func TestFilterCommandSilenceSendV2(t *testing.T) { + reset() + passiveCommands = make(map[string]*customCommand) + ping := func(cmd *PassiveCmd) (string, error) { return "pong", nil } + silenced := func(cmd *FilterCmd) (string, error) { return "", nil } + errored := func(cmd *FilterCmd) (string, error) { return "Ignored", errors.New("error") } + + RegisterPassiveCommand("ping", ping) + RegisterFilterCommand("silenced", silenced) + RegisterFilterCommand("errored", errored) + + b := newBotV2() + b.MessageReceived(&ChannelData{Channel: "#go-bot"}, &Message{Text: "test"}, &User{Nick: "user"}) + + waitMessages(t, 0, 1) + + if len(msgs) != 0 { + t.Fatal("Expected no messages!") + } + if len(errs) != 1 { + t.Error("Expected 1 error") + } +} + // how to test channels.. // https://www.hugopicado.com/2016/10/01/testing-over-golang-channels.html diff --git a/help.go b/help.go index 3ea8cb3..859b42e 100644 --- a/help.go +++ b/help.go @@ -14,7 +14,10 @@ const ( ) func (b *Bot) help(c *Cmd) { - cmd, _ := parse(CmdPrefix+c.RawArgs, c.ChannelData, c.User) + msg := &Message{ + Text: CmdPrefix + c.RawArgs, + } + cmd, _ := parse(msg, c.ChannelData, c.User) if cmd == nil { b.showAvailabeCommands(c.Channel, c.User) return diff --git a/parser.go b/parser.go index 883d372..09e6e8f 100644 --- a/parser.go +++ b/parser.go @@ -13,21 +13,20 @@ var ( re = regexp.MustCompile("\\s+") // Matches one or more spaces ) -func parse(s string, channel *ChannelData, user *User) (*Cmd, error) { - c := &Cmd{Raw: s} - s = strings.TrimSpace(s) +func parse(m *Message, channel *ChannelData, user *User) (*Cmd, error) { + s := strings.TrimSpace(m.Text) if !strings.HasPrefix(s, CmdPrefix) { return nil, nil } - c.Channel = strings.TrimSpace(channel.Channel) - c.ChannelData = channel - c.User = user - - // Trim the prefix and extra spaces - c.Message = strings.TrimPrefix(s, CmdPrefix) - c.Message = strings.TrimSpace(c.Message) + c := &Cmd{ + Channel: strings.TrimSpace(channel.Channel), + ChannelData: channel, + Message: strings.TrimSpace(strings.TrimPrefix(s, CmdPrefix)), + Raw: m.Text, + User: user, + } // check if we have the command and not only the prefix if c.Message == "" { @@ -48,9 +47,8 @@ func parse(s string, channel *ChannelData, user *User) (*Cmd, error) { c.Args = parsedArgs } - c.MessageData = &Message{ - Text: c.Message, - } + m.Text = c.Message + c.MessageData = m return c, nil } diff --git a/parser_test.go b/parser_test.go index 2bd5b27..8d81d7d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -98,7 +98,7 @@ func TestParser(t *testing.T) { for _, test := range tests { t.Run(test.msg, func(t *testing.T) { - cmd, _ := parse(test.msg, channel, user) + cmd, _ := parse(&Message{Text: test.msg}, channel, user) if test.expected != nil && cmd != nil { if test.expected.Raw != cmd.Raw { t.Errorf("Expected Raw:\n%#v\ngot:\n%#v", test.expected.Raw, cmd.Raw) @@ -156,7 +156,11 @@ func TestParser(t *testing.T) { } func TestInvalidArguments(t *testing.T) { - cmd, err := parse("!cmd Invalid \"arg", &ChannelData{Channel: "#go-bot"}, &User{Nick: "user123"}) + cmd, err := parse( + &Message{Text: "!cmd Invalid \"arg"}, + &ChannelData{Channel: "#go-bot"}, + &User{Nick: "user123"}, + ) if err == nil { t.Error("Expected error, got nil") } diff --git a/slack/slack.go b/slack/slack.go index 733068c..3478293 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -31,8 +31,26 @@ func defaultMessageFilter(message string, _ *bot.User) (string, slack.PostMessag func responseHandler(target string, message string, sender *bot.User) { message, params := messageFilter(message, sender) - _, _, err := api.PostMessage(target, slack.MsgOptionPostMessageParameters(params), - slack.MsgOptionText(message, false)) + _, _, err := api.PostMessage( + target, + slack.MsgOptionPostMessageParameters(params), + slack.MsgOptionText(message, false), + ) + if err != nil { + fmt.Printf("Error sending a slack message: %s\n", err.Error()) + } +} + +func responseHandlerV2(om bot.OutgoingMessage) { + message, params := messageFilter(om.Message, om.Sender) + if pmp, ok := om.ProtoParams.(*slack.PostMessageParameters); ok { + params = *pmp + } + _, _, err := api.PostMessage( + om.Target, + slack.MsgOptionPostMessageParameters(params), + slack.MsgOptionText(message, false), + ) if err != nil { fmt.Printf("Error sending a slack message: %s\n", err.Error()) } @@ -74,7 +92,9 @@ func extractUser(event *slack.MessageEvent) *bot.User { } func extractText(event *slack.MessageEvent) *bot.Message { - msg := &bot.Message{} + msg := &bot.Message{ + ProtoMsg: event, + } if len(event.Text) != 0 { msg.Text = event.Text if event.SubType == "me_message" { @@ -130,7 +150,8 @@ func Run(token string) { teaminfo, _ = api.GetTeamInfo() b := bot.New(&bot.Handlers{ - Response: responseHandler, + Response: responseHandler, + ResponseV2: responseHandlerV2, }, &bot.Config{ Protocol: protocol, @@ -156,24 +177,26 @@ Loop: readChannelData(api) case *slack.MessageEvent: - if !ev.Hidden && !ownMessage(ev.User) { - C := channelList[ev.Channel] - var channel = ev.Channel - if C.IsChannel { - channel = fmt.Sprintf("#%s", C.Name) - } - go b.MessageReceived( - &bot.ChannelData{ - Protocol: "slack", - Server: teaminfo.Domain, - Channel: channel, - HumanName: C.Name, - IsPrivate: !C.IsChannel, - }, - extractText(ev), - extractUser(ev), - ) + if ev.Hidden || ownMessage(ev.User) { + continue + } + + C := channelList[ev.Channel] + var channel = ev.Channel + if C.IsChannel { + channel = fmt.Sprintf("#%s", C.Name) } + go b.MessageReceived( + &bot.ChannelData{ + Protocol: protocol, + Server: teaminfo.Domain, + Channel: channel, + HumanName: C.Name, + IsPrivate: !C.IsChannel, + }, + extractText(ev), + extractUser(ev), + ) case *slack.RTMError: fmt.Printf("Error: %s\n", ev.Error()) From 51bcba0468630bfec578c5dbeef675f3dbbee567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Fri, 13 Sep 2019 09:53:21 -0300 Subject: [PATCH 31/42] Update dependencies --- go.mod | 31 ++++++---- go.sum | 183 +++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 159 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index f7f3b97..ecef26c 100644 --- a/go.mod +++ b/go.mod @@ -1,33 +1,42 @@ module github.com/go-chat-bot/bot require ( - cloud.google.com/go v0.32.0 + cloud.google.com/go v0.38.0 + github.com/Jeffail/gabs v1.4.0 // indirect github.com/andygrunwald/go-jira v1.5.0 // indirect github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 // indirect github.com/cloudfoundry/gosigar v1.1.0 // indirect + github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 // indirect github.com/fatih/structs v1.0.0 // indirect github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc + github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect + github.com/google/go-cmp v0.3.1 // indirect github.com/google/go-querystring v1.0.0 // indirect - github.com/googleapis/gax-go v2.0.0+incompatible // indirect + github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect github.com/gorilla/websocket v1.4.0 // indirect + github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect + github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect - github.com/mattn/go-shellwords v1.0.3 - github.com/mozillazg/go-unidecode v0.1.0 + github.com/mattn/go-shellwords v1.0.6 + github.com/mozillazg/go-unidecode v0.1.1 github.com/nlopes/slack v0.5.0 + github.com/onsi/ginkgo v1.10.1 // indirect + github.com/onsi/gomega v1.7.0 // indirect github.com/pkg/errors v0.8.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f github.com/robfig/cron/v3 v3.0.0 + github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect + github.com/stretchr/testify v1.4.0 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 github.com/trivago/tgo v1.0.5 // indirect - go.opencensus.io v0.18.0 // indirect - golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect - golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e // indirect - google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93 // indirect - google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect + go.opencensus.io v0.22.1 // indirect + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 // indirect + google.golang.org/api v0.10.0 // indirect + google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 // indirect + google.golang.org/grpc v1.23.1 // indirect gopkg.in/telegram-bot-api.v3 v3.0.0 ) diff --git a/go.sum b/go.sum index d9dcb35..098ed75 100644 --- a/go.sum +++ b/go.sum @@ -1,100 +1,195 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.32.0 h1:DSt59WoyNcfAInilEpfvm2ugq8zvNyaHAm9MkzOwRQ4= -cloud.google.com/go v0.32.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= +github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= github.com/andygrunwald/go-jira v1.5.0 h1:/1CyYLNdwus7TvB/DHyD3udb52K12aYL9m7WaGAO9m4= github.com/andygrunwald/go-jira v1.5.0/go.mod h1:yNYQrX3nGSrVdcVsM2mWz2pm7tTeDtYfRyVEkc3VUiY= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry/gosigar v1.1.0 h1:V/dVCzhKOdIU3WRB5inQU20s4yIgL9Dxx/Mhi0SF8eM= github.com/cloudfoundry/gosigar v1.1.0/go.mod h1:3qLfc2GlfmwOx2+ZDaRGH3Y9fwQ0sQeaAleo2GV5pH0= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 h1:zwp9mAr+YvsgLCFIVJ3/m61Z+NRX35jbD0HBa62ryHY= +github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2/go.mod h1:w5eKhlAkZwY6VBm2Sa1Evdte2+Fqhc+dnSk7/KTN5FM= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 h1:qNYjVQnDwznjLk+OnNdczA5SXwEa/RwjPTZSQCKofF4= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc h1:v/poG4Y4O/z1cUm2cWxiIkFFgRsT3Fe1u1A33evx89g= github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo= +github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= +github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NPZopPfCc1obT6aY3SVM6DOctz1A9ZU= +github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCGOsuuMnn7P2X9dGh3meUNASx6kHIpe7A= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= -github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mozillazg/go-unidecode v0.1.0 h1:wAIMDf/yTexXKxT5TkctLwmClGSyuoJaZDRMclFNq8U= -github.com/mozillazg/go-unidecode v0.1.0/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= -github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA= -github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE= +github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +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/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaNHbtrHW9ZuzrilETIabr9jPNj3Zs= github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= -github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= -github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQMTscCQ7VH3JAVuxb/pe53v2LBiA7z8= github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3/go.mod h1:QYOctLs5qEsaIrA/PKEc4YqAv2SozbxNEX0vMPs84p4= github.com/trivago/tgo v1.0.5 h1:ihzy8zFF/LPsd8oxsjYOE8CmyOTNViyFCy0EaFreUIk= github.com/trivago/tgo v1.0.5/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= -go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= -golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE= +golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93 h1:TSNRyeWkPS9y+f2F+lO1Yfn9VaFA8MGMUPvn4dXqia0= -google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.10.0 h1:7tmAxx3oKE98VMZ+SBZzvYYWRQ9HODBxmC8mXUsraSQ= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b h1:WkFtVmaZoTRVoRYr0LTC9SYNhlw0X0HrVPz2OVssVm4= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 h1:Ex1mq5jaJof+kRnYi3SlYJ8KKa9Ao3NHyIT5XJ1gF6U= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/telegram-bot-api.v3 v3.0.0 h1:Y6QmqOMwRKv5NUdlvzEBtEZChjsrqdTS6O858cvuCww= gopkg.in/telegram-bot-api.v3 v3.0.0/go.mod h1:WxP4rAHcQNrXhQLGIK9aVLkpygV4Qq8YS3yjjJ/0VLA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 9254c8eebc18359083ca25779f043b8db500d376 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 30 Sep 2019 08:15:33 -0400 Subject: [PATCH 32/42] Use SendMessageV2 everywhere (#116) Then rename SendMessageV2 to SendMessage because it does the same thing. This is a breaking change, but it is an easy one to fix, so I want to do it anyway. This isn't necessary everywhere, but for standardization, I thought it would be good to really deprecate SendMessageV1. The place this IS needed is in PeriodicCommands v2 and PassiveCommands V2. In both those places, objects come back from the caller with ProtoParams that should be forwarded through to the message sending. --- bot.go | 30 ++++++++++++++++-------------- cmd.go | 35 +++++++++++++++++++++++++++-------- google-chat/google-chat.go | 5 ++++- help.go | 24 ++++++++++++++++++++---- 4 files changed, 67 insertions(+), 27 deletions(-) diff --git a/bot.go b/bot.go index 76c8c10..4266096 100644 --- a/bot.go +++ b/bot.go @@ -148,7 +148,10 @@ func (b *Bot) startPeriodicCommands() { if err != nil { b.errored("Periodic command failed ", err) } else if message != "" { - b.SendMessage(channel, message, nil) + b.SendMessage(OutgoingMessage{ + Target: channel, + Message: message, + }) } } case v2: @@ -158,7 +161,11 @@ func (b *Bot) startPeriodicCommands() { return } for _, result := range results { - b.SendMessage(result.Channel, result.Message, nil) + b.SendMessage(OutgoingMessage{ + Target: result.Channel, + Message: result.Message, + ProtoParams: result.ProtoParams, + }) } } }) @@ -173,7 +180,11 @@ func (b *Bot) startPeriodicCommands() { func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *User) { command, err := parse(message, channel, sender) if err != nil { - b.SendMessage(channel.Channel, err.Error(), sender) + b.SendMessage(OutgoingMessage{ + Target: channel.Channel, + Message: err.Error(), + Sender: sender, + }) return } @@ -200,17 +211,8 @@ func (b *Bot) MessageReceived(channel *ChannelData, message *Message, sender *Us } } -// SendMessage queues a message for a target recipient, optionally from a particular sender. -func (b *Bot) SendMessage(target string, message string, sender *User) { - b.SendMessageV2(OutgoingMessage{ - Target: target, - Message: message, - Sender: sender, - }) -} - -// SendMessageV2 queues a message. -func (b *Bot) SendMessageV2(om OutgoingMessage) { +// SendMessage queues a message. +func (b *Bot) SendMessage(om OutgoingMessage) { message := b.executeFilterCommands(&FilterCmd{ Target: om.Target, Message: om.Message, diff --git a/cmd.go b/cmd.go index 2d754fa..d8467e8 100644 --- a/cmd.go +++ b/cmd.go @@ -319,7 +319,11 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { if err != nil { b.errored(fmt.Sprintf("Error executing %s", cmdFunc.Cmd), err) } else { - b.SendMessage(cmd.Channel, result, cmd.User) + b.SendMessage(OutgoingMessage{ + Target: cmd.Channel, + Message: result, + Sender: cmd.User, + }) } case pv2: result, err := cmdFunc.PassiveFuncV2(cmd) @@ -327,12 +331,16 @@ func (b *Bot) executePassiveCommands(cmd *PassiveCmd) { b.errored(fmt.Sprintf("Error executing %s", cmdFunc.Cmd), err) return } - for { select { case message := <-result.Message: if message != "" { - b.SendMessage(result.Channel, message, cmd.User) + b.SendMessage(OutgoingMessage{ + Target: result.Channel, + Message: message, + Sender: cmd.User, + ProtoParams: result.ProtoParams, + }) } case <-result.Done: return @@ -382,7 +390,11 @@ func (b *Bot) handleCmd(c *Cmd) { message, err := cmd.CmdFuncV1(c) b.checkCmdError(err, c) if message != "" { - b.SendMessage(c.Channel, message, c.User) + b.SendMessage(OutgoingMessage{ + Target: c.Channel, + Message: message, + Sender: c.User, + }) } case v2: result, err := cmd.CmdFuncV2(c) @@ -392,7 +404,7 @@ func (b *Bot) handleCmd(c *Cmd) { } if result.Message != "" { - b.SendMessageV2(OutgoingMessage{ + b.SendMessage(OutgoingMessage{ Target: result.Channel, Message: result.Message, Sender: c.User, @@ -409,7 +421,7 @@ func (b *Bot) handleCmd(c *Cmd) { select { case message := <-result.Message: if message != "" { - b.SendMessageV2(OutgoingMessage{ + b.SendMessage(OutgoingMessage{ Target: result.Channel, Message: message, Sender: c.User, @@ -427,7 +439,11 @@ func (b *Bot) checkCmdError(err error, c *Cmd) { if err != nil { errorMsg := fmt.Sprintf(errorExecutingCommand, c.Command, err.Error()) b.errored(errorMsg, err) - b.SendMessage(c.Channel, errorMsg, c.User) + b.SendMessage(OutgoingMessage{ + Target: c.Channel, + Message: errorMsg, + Sender: c.User, + }) } } @@ -457,7 +473,10 @@ func (b *Bot) handleMessageStream(streamName string, ms *MessageStream) { continue } if d.Message != "" { - b.SendMessage(d.ChannelData.Channel, d.Message, nil) + b.SendMessage(OutgoingMessage{ + Target: d.ChannelData.Channel, + Message: d.Message, + }) } case <-ms.Done: return diff --git a/google-chat/google-chat.go b/google-chat/google-chat.go index dc91b01..087620e 100644 --- a/google-chat/google-chat.go +++ b/google-chat/google-chat.go @@ -135,7 +135,10 @@ func Run(config *Config) { case "ADDED_TO_SPACE": if config.WelcomeMessage != "" { log.Printf("Sending welcome message to %s\n", msg.Space.Name) - b.SendMessage(msg.Space.Name, config.WelcomeMessage, nil) + b.SendMessage(OutgoingMessage{ + Target: msg.Space.Name, + Message: config.WelcomeMessage, + }) } case "REMOVED_FROM_SPACE": break diff --git a/help.go b/help.go index 859b42e..4bf2f10 100644 --- a/help.go +++ b/help.go @@ -34,9 +34,17 @@ func (b *Bot) help(c *Cmd) { func (b *Bot) showHelp(c *Cmd, help *customCommand) { if help.Description != "" { - b.SendMessage(c.Channel, fmt.Sprintf(helpDescripton, help.Description), c.User) + b.SendMessage(OutgoingMessage{ + Target: c.Channel, + Message: fmt.Sprintf(helpDescripton, help.Description), + Sender: c.User, + }) } - b.SendMessage(c.Channel, fmt.Sprintf(helpUsage, CmdPrefix, c.Command, help.ExampleArgs), c.User) + b.SendMessage(OutgoingMessage{ + Target: c.Channel, + Message: fmt.Sprintf(helpUsage, CmdPrefix, c.Command, help.ExampleArgs), + Sender: c.User, + }) } func (b *Bot) showAvailabeCommands(channel string, sender *User) { @@ -44,6 +52,14 @@ func (b *Bot) showAvailabeCommands(channel string, sender *User) { for k := range commands { cmds = append(cmds, k) } - b.SendMessage(channel, fmt.Sprintf(helpAboutCommand, CmdPrefix), sender) - b.SendMessage(channel, fmt.Sprintf(availableCommands, strings.Join(cmds, ", ")), sender) + b.SendMessage(OutgoingMessage{ + Target: channel, + Message: fmt.Sprintf(helpAboutCommand, CmdPrefix), + Sender: sender, + }) + b.SendMessage(OutgoingMessage{ + Target: channel, + Message: fmt.Sprintf(availableCommands, strings.Join(cmds, ", ")), + Sender: sender, + }) } From e7f16ed71d1dcfabaaa56b58f93e06b5c04ff256 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 30 Sep 2019 08:16:55 -0400 Subject: [PATCH 33/42] Slack: Add methods to directly add or remove reactions (#117) These methods are simple helper methods to expose Slack emoji reactions to bot commands. Any command that wants to call these methods obviously is responsible for ensuring that their command is only used in Slack use cases. My favorite use case for this is to have the bot auto react to usages of @here with :please-no-at-here:. Future Plans: Build a full Reaction Router pattern so that the bots can register commands to run when a message is tagged with a reaction. For example: https://reacji-channeler.builtbyslack.com/ --- slack/slack.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/slack/slack.go b/slack/slack.go index 3478293..e35c0e6 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -56,6 +56,26 @@ func responseHandlerV2(om bot.OutgoingMessage) { } } +// AddReactionToMessage allows you to add a reaction, to a message. +func AddReactionToMessage(msgid, channel string, reaction string) error { + toReact := slack.ItemRef{ + Timestamp: msgid, + Channel: channel, + } + + return api.AddReaction(reaction, toReact) +} + +// RemoveReactionFromMessage allows you to remove a reaction, from a message. +func RemoveReactionFromMessage(msgid, channel string, reaction string) error { + reactionRef := slack.ItemRef{ + Timestamp: msgid, + Channel: channel, + } + + return api.RemoveReaction(reaction, reactionRef) +} + // FindUserBySlackID converts a slack.User into a bot.User struct func FindUserBySlackID(userID string) *bot.User { slackUser, err := api.GetUserInfo(userID) From 42c277dd4492ef0d998c25eb3410eef4667c634a Mon Sep 17 00:00:00 2001 From: "Jay R. Wren" Date: Thu, 3 Oct 2019 07:14:01 -0400 Subject: [PATCH 34/42] fix the build (#118) --- google-chat/google-chat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-chat/google-chat.go b/google-chat/google-chat.go index 087620e..eab41ce 100644 --- a/google-chat/google-chat.go +++ b/google-chat/google-chat.go @@ -135,7 +135,7 @@ func Run(config *Config) { case "ADDED_TO_SPACE": if config.WelcomeMessage != "" { log.Printf("Sending welcome message to %s\n", msg.Space.Name) - b.SendMessage(OutgoingMessage{ + b.SendMessage(bot.OutgoingMessage{ Target: msg.Space.Name, Message: config.WelcomeMessage, }) From 3da6cae4547763aa55828a9f75ed4693bed5d36f Mon Sep 17 00:00:00 2001 From: Mauricio Teixeira Date: Tue, 22 Oct 2019 09:05:43 -0400 Subject: [PATCH 35/42] Hangouts API no longer sends verification tokens (#119) --- README.md | 4 +--- google-chat/google-chat.go | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index f2150eb..16e3557 100644 --- a/README.md +++ b/README.md @@ -185,14 +185,13 @@ Condensed, the steps you will need to take are as follows: * Choose "Pub/Sub Editor" role for the credential * Enable Pub/Sub API in cloud console * Create new topic in the Pub/Sub (say "google-chat") - * This is Config.TopicName + * Use the value of "Topic Name" (**not** "Topic ID") for Config.TopicName (e.g. /project//topics/) * Modify permissions on created topic so that "chat-api-push@system.gserviceaccount.com" has Pub/Sub Publisher permissions * Enable hangouts chat api in Cloud Console * Go to hangouts chat API config in the Cloud Console and fill in info * Connection settings - use Pub/Sub and fill in topic string you created above - * Verification token is your Config.Token Config.SubscriptionName should be unique for each environment or you'll not process messages correctly. If you encounter issues make sure your credentials @@ -220,7 +219,6 @@ func main() { TopicName: os.Getenv("HANGOUTS_TOPIC"), SubscriptionName: os.Getenv("HANGOUTS_SUB"), WelcomeMessage: os.Getenv("HANGOUTS_WELCOME"), - Token: os.Getenv("HANGOUTS_TOKEN")}) } ``` diff --git a/google-chat/google-chat.go b/google-chat/google-chat.go index eab41ce..7b2a09c 100644 --- a/google-chat/google-chat.go +++ b/google-chat/google-chat.go @@ -31,7 +31,6 @@ type Config struct { PubSubProject string TopicName string SubscriptionName string - Token string WelcomeMessage string } @@ -122,11 +121,6 @@ func Run(config *Config) { m.Ack() return } - if msg.Token != config.Token { - log.Printf("Failed to verify token: %s", msg.Token) - m.Ack() - return - } log.Printf("Space: %s (%s)\n", msg.Space.Name, msg.Space.DisplayName) log.Printf("Message type: %s\n", msg.Type) From 272d0f24a45f56e1ebbb285472f9f7998421f552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Ochotnick=C3=BD?= Date: Sun, 1 Dec 2019 23:55:43 +0100 Subject: [PATCH 36/42] Strip bot nickname from beginning of messages (#120) When one channel has multiple go-chat-bot instances this allows to direct commands to a specific one while other bots would ignore it. It could be made configurable so that by default no such stripping would occur if there are compatibility worries with some plugins. --- irc/irc.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/irc/irc.go b/irc/irc.go index 01e56a7..b5b62f4 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -3,7 +3,9 @@ package irc import ( "crypto/tls" + "fmt" "log" + "regexp" "strings" "github.com/go-chat-bot/bot" @@ -24,9 +26,10 @@ type Config struct { } var ( - ircConn *ircevent.Connection - config *Config - b *bot.Bot + ircConn *ircevent.Connection + config *Config + b *bot.Bot + nickStartRE *regexp.Regexp ) const protocol = "irc" @@ -52,7 +55,7 @@ func onPRIVMSG(e *ircevent.Event) { Channel: e.Arguments[0], IsPrivate: e.Arguments[0] == ircConn.GetNick()}, &bot.Message{ - Text: e.Message(), + Text: nickStartRE.ReplaceAllString(e.Message(), ""), }, &bot.User{ ID: e.Host, @@ -116,6 +119,8 @@ func SetUp(c *Config) *bot.Bot { Server: c.Server, }, ) + // prepare regex to strip from messages - nick followed by colon/comma and spaces + nickStartRE = regexp.MustCompile(fmt.Sprintf("%s[,:] *", c.Nick)) ircConn.AddCallback("001", onWelcome) ircConn.AddCallback("PRIVMSG", onPRIVMSG) From d3ed426b69da98b1cb33d1c6fe36644f32aa45f5 Mon Sep 17 00:00:00 2001 From: Alex Giesa <28443176+Alex77g@users.noreply.github.com> Date: Mon, 4 May 2020 12:23:09 +0200 Subject: [PATCH 37/42] slack dependency updated (#124) Co-authored-by: Alexander Giesa --- go.mod | 5 +---- go.sum | 12 ++++++------ slack/slack.go | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ecef26c..9005cd6 100644 --- a/go.mod +++ b/go.mod @@ -15,17 +15,14 @@ require ( github.com/google/go-querystring v1.0.0 // indirect github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect github.com/gorilla/websocket v1.4.0 // indirect - github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect - github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect github.com/mattn/go-shellwords v1.0.6 github.com/mozillazg/go-unidecode v0.1.1 - github.com/nlopes/slack v0.5.0 github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect - github.com/pkg/errors v0.8.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f github.com/robfig/cron/v3 v3.0.0 + github.com/slack-go/slack v0.6.4 github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect github.com/stretchr/testify v1.4.0 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect diff --git a/go.sum b/go.sum index 098ed75..703602b 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc h1:v/poG4Y4 github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= @@ -55,6 +57,7 @@ github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbV github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -65,18 +68,12 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdl github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= -github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= -github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NPZopPfCc1obT6aY3SVM6DOctz1A9ZU= -github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCGOsuuMnn7P2X9dGh3meUNASx6kHIpe7A= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE= github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= -github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= -github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -90,11 +87,14 @@ github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaN github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/slack-go/slack v0.6.4 h1:cxOqFgM5RW6mdEyDqAJutFk3qiORK9oHRKi5bPqkY9o= +github.com/slack-go/slack v0.6.4/go.mod h1:sGRjv3w+ERAUMMMbldHObQPBcNSyVB7KLKYfnwUFBfw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= diff --git a/slack/slack.go b/slack/slack.go index e35c0e6..25a6e47 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/go-chat-bot/bot" - "github.com/nlopes/slack" + "github.com/slack-go/slack" ) // MessageFilter allows implementing a filter function to transform the messages From ef71c72a524ae1242a47163d87b52dda69583bf6 Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Wed, 27 May 2020 14:14:14 -0400 Subject: [PATCH 38/42] Parser: Use Fields to account for all unicode white spaces (#126) strings.SplitN was only matching U+0020 for splitting command and args Use strings.FieldsFunc to check unicode.IsSpace to account for all unicode white spaces reference: https://golang.org/pkg/strings/#FieldsFunc --- parser.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 09e6e8f..6873fd5 100644 --- a/parser.go +++ b/parser.go @@ -4,6 +4,7 @@ import ( "errors" "regexp" "strings" + "unicode" "github.com/mattn/go-shellwords" unidecode "github.com/mozillazg/go-unidecode" @@ -33,8 +34,17 @@ func parse(m *Message, channel *ChannelData, user *User) (*Cmd, error) { return nil, nil } + firstOccurrence := true + firstUnicodeSpace := func(c rune) bool { + isFirstSpace := unicode.IsSpace(c) && firstOccurrence + if isFirstSpace { + firstOccurrence = false + } + return isFirstSpace + } + // get the command - pieces := strings.SplitN(c.Message, " ", 2) + pieces := strings.FieldsFunc(c.Message, firstUnicodeSpace) c.Command = strings.ToLower(unidecode.Unidecode(pieces[0])) if len(pieces) > 1 { From d1d8f9423c8f2696897e9270d04dd55156bc00d6 Mon Sep 17 00:00:00 2001 From: NIcecream <31983334+NeapolitanIcecream@users.noreply.github.com> Date: Fri, 7 Aug 2020 22:46:42 +0800 Subject: [PATCH 39/42] Replace method GetChannel() due to deprecation (#128) Fixed code finally calls [channels.list](https://api.slack.com/methods/channels.list) which is deprecated. Newly created apps after June 10th, 2020 can not work with it. --- slack/slack.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slack/slack.go b/slack/slack.go index 25a6e47..6a5c2e4 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -139,7 +139,8 @@ func readBotInfo(api *slack.Client) { } func readChannelData(api *slack.Client) { - channels, err := api.GetChannels(true) + params := slack.GetConversationsParameters{} + channels, _, err := api.GetConversations(¶ms) if err != nil { fmt.Printf("Error getting Channels: %s\n", err) return From 763f9eeac7d5d43ac3ddce8cf2a32490d6757bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Sun, 4 Oct 2020 11:12:19 -0300 Subject: [PATCH 40/42] Update plugins --- debug/main.go | 1 + go.mod | 2 +- go.sum | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/debug/main.go b/debug/main.go index 169145b..5dcee54 100644 --- a/debug/main.go +++ b/debug/main.go @@ -16,6 +16,7 @@ import ( _ "github.com/go-chat-bot/plugins/catfacts" _ "github.com/go-chat-bot/plugins/catgif" _ "github.com/go-chat-bot/plugins/chucknorris" + _ "github.com/go-chat-bot/plugins-br/gloria_a_deus" _ "github.com/go-chat-bot/plugins/cmd" _ "github.com/go-chat-bot/plugins/crypto" _ "github.com/go-chat-bot/plugins/encoding" diff --git a/go.mod b/go.mod index 9005cd6..ac1180c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 // indirect github.com/fatih/structs v1.0.0 // indirect github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 - github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc + github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/google/go-cmp v0.3.1 // indirect github.com/google/go-querystring v1.0.0 // indirect diff --git a/go.sum b/go.sum index 703602b..9764270 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,9 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 h1:qNYjVQnDwznjLk+OnNdczA5SXwEa/RwjPTZSQCKofF4= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= -github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc h1:v/poG4Y4O/z1cUm2cWxiIkFFgRsT3Fe1u1A33evx89g= -github.com/go-chat-bot/plugins-br v0.0.0-20170316122923-eb41b30907dc/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= +github.com/go-chat-bot/plugins v0.0.0-20200616122335-c0aba236acdb h1:k2HBEaNC/+9L6d8iA+ls7EMIS6u2SRwhXlxTgfRIEvk= +github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584 h1:6i1FuHDlNZ6OtsEeE6GKa7e078pbXafYbnvo1PG3WHw= +github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= From 9f4d3ace4f0be6f5d05d1621d439133e13443435 Mon Sep 17 00:00:00 2001 From: Fabio Gomes Date: Thu, 27 Jan 2022 09:13:39 -0300 Subject: [PATCH 41/42] Update dependencies --- go.mod | 44 ++--- go.sum | 585 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 591 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index ac1180c..77e02b7 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,41 @@ module github.com/go-chat-bot/bot require ( - cloud.google.com/go v0.38.0 + cloud.google.com/go v0.100.2 + cloud.google.com/go/compute v1.1.0 // indirect + cloud.google.com/go/iam v0.1.1 // indirect + cloud.google.com/go/pubsub v1.17.1 github.com/Jeffail/gabs v1.4.0 // indirect - github.com/andygrunwald/go-jira v1.5.0 // indirect + github.com/andygrunwald/go-jira v1.14.0 // indirect github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 // indirect - github.com/cloudfoundry/gosigar v1.1.0 // indirect + github.com/cloudfoundry/gosigar v1.3.3 // indirect github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 // indirect - github.com/fatih/structs v1.0.0 // indirect - github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 + github.com/go-chat-bot/plugins v0.0.0-20210423135617-90af38949890 github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect - github.com/google/go-cmp v0.3.1 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect - github.com/gorilla/websocket v1.4.0 // indirect - github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 // indirect - github.com/mattn/go-shellwords v1.0.6 + github.com/martinusso/go-docs v1.0.0 // indirect + github.com/mattn/go-shellwords v1.0.12 github.com/mozillazg/go-unidecode v0.1.1 github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f - github.com/robfig/cron/v3 v3.0.0 - github.com/slack-go/slack v0.6.4 + github.com/robfig/cron/v3 v3.0.1 + github.com/slack-go/slack v0.10.1 github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect - github.com/stretchr/testify v1.4.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 - github.com/trivago/tgo v1.0.5 // indirect - go.opencensus.io v0.22.1 // indirect - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 // indirect - google.golang.org/api v0.10.0 // indirect - google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 // indirect - google.golang.org/grpc v1.23.1 // indirect + github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 + golang.org/x/net v0.0.0-20220127074510-2fabfed7e28f // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect + google.golang.org/grpc v1.44.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/telegram-bot-api.v3 v3.0.0 ) diff --git a/go.sum b/go.sum index 9764270..f9c7aa8 100644 --- a/go.sum +++ b/go.sum @@ -1,59 +1,216 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.1.0 h1:pyPhehLfZ6pVzRgJmXGYvCY4K7WSWRhVw0AwhgVvS84= +cloud.google.com/go/compute v1.1.0/go.mod h1:2NIffxgWfORSI7EOYMFatGTfjMLnqrOKBEyYb6NoRgA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68= +cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= +cloud.google.com/go/kms v1.0.0 h1:YkIeqPXqTAlwXk3Z2/WG0d6h1tqJQjU354WftjEoP9E= +cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.17.1 h1:s2UGTTphpnUQ0Wppkp2OprR4pS3nlBpPvyL2GV9cqdc= +cloud.google.com/go/pubsub v1.17.1/go.mod h1:4qDxMr1WsM9+aQAz36ltDwCIM+R0QdlseyFjBuNvnss= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/andygrunwald/go-jira v1.5.0 h1:/1CyYLNdwus7TvB/DHyD3udb52K12aYL9m7WaGAO9m4= github.com/andygrunwald/go-jira v1.5.0/go.mod h1:yNYQrX3nGSrVdcVsM2mWz2pm7tTeDtYfRyVEkc3VUiY= +github.com/andygrunwald/go-jira v1.14.0 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI= +github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry/gosigar v1.1.0 h1:V/dVCzhKOdIU3WRB5inQU20s4yIgL9Dxx/Mhi0SF8eM= github.com/cloudfoundry/gosigar v1.1.0/go.mod h1:3qLfc2GlfmwOx2+ZDaRGH3Y9fwQ0sQeaAleo2GV5pH0= +github.com/cloudfoundry/gosigar v1.3.3 h1:aR9qIZ/Njb4GYPUGnoJMfXdhz+AOy80r4r3LUQC5A0g= +github.com/cloudfoundry/gosigar v1.3.3/go.mod h1:4TGthjsfIxe6Svlzn48EUw9xTTjT/2NnWyeRrferFP0= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 h1:zwp9mAr+YvsgLCFIVJ3/m61Z+NRX35jbD0HBa62ryHY= github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2/go.mod h1:w5eKhlAkZwY6VBm2Sa1Evdte2+Fqhc+dnSk7/KTN5FM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6 h1:qNYjVQnDwznjLk+OnNdczA5SXwEa/RwjPTZSQCKofF4= github.com/go-chat-bot/plugins v0.0.0-20181006134258-491b3f9878d6/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= -github.com/go-chat-bot/plugins v0.0.0-20200616122335-c0aba236acdb h1:k2HBEaNC/+9L6d8iA+ls7EMIS6u2SRwhXlxTgfRIEvk= +github.com/go-chat-bot/plugins v0.0.0-20210423135617-90af38949890 h1:HOa74NO1AF9Gn+K0rQInzzol0ja9NCTkX0xlfmG8PkU= +github.com/go-chat-bot/plugins v0.0.0-20210423135617-90af38949890/go.mod h1:Ga63x4EC4NFYr/KGzhn8D8fLj89sfJA/dpBsuowiHOQ= github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584 h1:6i1FuHDlNZ6OtsEeE6GKa7e078pbXafYbnvo1PG3WHw= github.com/go-chat-bot/plugins-br v0.0.0-20200917130500-b69d8e0f9584/go.mod h1:KU0Ieo/D/HBwPY6n3tLWanM5GemW6iWXdbgm96qRW2Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo= github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -61,136 +218,530 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58 h1:VmcrkkMjTdCGOsuuMnn7P2X9dGh3meUNASx6kHIpe7A= github.com/martinusso/go-docs v0.0.0-20161215163720-81905d575a58/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= -github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/martinusso/go-docs v1.0.0 h1:4NTC5WegPzZAl51EM8V8Y78HjC3adRu3qfJIW9Q2idQ= +github.com/martinusso/go-docs v1.0.0/go.mod h1:QymHbiLXXhrSGV5xTWYfEBt9mau3hHwVOT9Y7tpolJU= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE= github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= +github.com/onsi/ginkgo v1.2.1-0.20160409220416-2c2e9bb47b4e/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.2.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f h1:N1r6pSlez3lLsqaNHbtrHW9ZuzrilETIabr9jPNj3Zs= github.com/pyinx/gorocket v0.0.0-20170810024322-78ae1353729f/go.mod h1:nh/AiOs8vRCaqnSOHVzyta23ZLm5ck/st4brrxtQJEo= -github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= -github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/slack-go/slack v0.6.4 h1:cxOqFgM5RW6mdEyDqAJutFk3qiORK9oHRKi5bPqkY9o= github.com/slack-go/slack v0.6.4/go.mod h1:sGRjv3w+ERAUMMMbldHObQPBcNSyVB7KLKYfnwUFBfw= +github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA= +github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQMTscCQ7VH3JAVuxb/pe53v2LBiA7z8= github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3/go.mod h1:QYOctLs5qEsaIrA/PKEc4YqAv2SozbxNEX0vMPs84p4= +github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 h1:l/T7dYuJEQZOwVOpjIXr1180aM9PZL/d1MnMVIxefX4= +github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64/go.mod h1:Q1NAJOuRdQCqN/VIWdnaaEhV8LpeO2rtlBP7/iDJNII= github.com/trivago/tgo v1.0.5 h1:ihzy8zFF/LPsd8oxsjYOE8CmyOTNViyFCy0EaFreUIk= github.com/trivago/tgo v1.0.5/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= +github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20171107184841-a337091b0525/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127074510-2fabfed7e28f h1:o66Bv9+w/vuk7Krcig9jZqD01FP7BL8OliFqqw0xzPI= +golang.org/x/net v0.0.0-20220127074510-2fabfed7e28f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.1.1-0.20171102192421-88f656faf3f3/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.10.0 h1:7tmAxx3oKE98VMZ+SBZzvYYWRQ9HODBxmC8mXUsraSQ= google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= +google.golang.org/api v0.65.0 h1:MTW9c+LIBAbwoS1Gb+YV7NjFBt2f7GtAS5hIzh2NjgQ= +google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 h1:Ex1mq5jaJof+kRnYi3SlYJ8KKa9Ao3NHyIT5XJ1gF6U= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 h1:YxHp5zqIcAShDEvRr5/0rVESVS+njYF68PSdazrNLJo= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/telegram-bot-api.v3 v3.0.0 h1:Y6QmqOMwRKv5NUdlvzEBtEZChjsrqdTS6O858cvuCww= gopkg.in/telegram-bot-api.v3 v3.0.0/go.mod h1:WxP4rAHcQNrXhQLGIK9aVLkpygV4Qq8YS3yjjJ/0VLA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 71bc1bfb492cec9afcf0aeb2f92170cec0d13d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Gomes?= Date: Thu, 27 Jan 2022 09:33:39 -0300 Subject: [PATCH 42/42] Update go-jira to master branch to support personal tokens (#139) --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 77e02b7..da83f57 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/iam v0.1.1 // indirect cloud.google.com/go/pubsub v1.17.1 github.com/Jeffail/gabs v1.4.0 // indirect - github.com/andygrunwald/go-jira v1.14.0 // indirect + github.com/andygrunwald/go-jira v1.14.1-0.20220125145100-3555edb9bbda // indirect github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 // indirect github.com/cloudfoundry/gosigar v1.3.3 // indirect github.com/detached/gorocket v0.0.0-20170629192631-d44bbd3f26d2 // indirect diff --git a/go.sum b/go.sum index f9c7aa8..f9eda67 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/andygrunwald/go-jira v1.5.0 h1:/1CyYLNdwus7TvB/DHyD3udb52K12aYL9m7WaG github.com/andygrunwald/go-jira v1.5.0/go.mod h1:yNYQrX3nGSrVdcVsM2mWz2pm7tTeDtYfRyVEkc3VUiY= github.com/andygrunwald/go-jira v1.14.0 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI= github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= +github.com/andygrunwald/go-jira v1.14.1-0.20220125145100-3555edb9bbda h1:Ia6b4e/0qTV1VcWCUECOFjdPTyRAaLhKf3q+ZVQKZoI= +github.com/andygrunwald/go-jira v1.14.1-0.20220125145100-3555edb9bbda/go.mod h1:m62VSchfJSdD0PNkndBdbclfKRPGbKYkPhDN3Spm90I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8= github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I= @@ -126,6 +128,8 @@ github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=