From 71f3eca99d9448d8e27fdbabd9a84b24e97cdc44 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Sun, 28 Jan 2024 16:15:58 +0100 Subject: [PATCH] add code --- .idea/.gitignore | 8 ++ .idea/modules.xml | 8 ++ .idea/slack-nb-bot.iml | 9 ++ AUTHORS | 3 + LICENSE | 13 +++ README.md | 75 ++++++++++++++ bot/main.go | 217 +++++++++++++++++++++++++++++++++++++++++ go.mod | 13 +++ go.sum | 25 +++++ 9 files changed, 371 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/slack-nb-bot.iml create mode 100644 AUTHORS create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bot/main.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4153495 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/slack-nb-bot.iml b/.idea/slack-nb-bot.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/slack-nb-bot.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f2b2287 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Mikhail Bragin (https://github.com/braginini) +Maycon Santos (https://github.com/mlsmaycon) +Wiretrustee UG (haftungsbeschränkt) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bddb455 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +BSD 3-Clause License + +Copyright (c) 2022 Wiretrustee UG (haftungsbeschränkt) & AUTHORS + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8196994 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# NetBird Slack Greeter + +This Go-based application, residing in the `bot` folder, is designed to integrate with Slack for the NetBird community. It handles events from Slack's Socket Mode, offering automated responses and channel management. + +## Features + +- **Automated Responses**: Automatically sends welcome messages to new users in specific channels or the entire workspace. +- **Template-based Messaging**: Employs predefined templates for different channels and events. +- **Event Handling**: Listens and responds to Slack events, like users joining a channel or the workspace. +- **Logging**: Uses `logrus` for structured logging, enhancing traceability and debugging. + +## Requirements + +- Go (tested with version 1.21.x or later, but probably work with older versions) +- Slack App Token (`SLACK_APP_TOKEN`) with the prefix `xapp-` +- Slack Bot Token (`SLACK_BOT_TOKEN`) with the prefix `xoxb-` + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/netbirdio/netbird-slack-greeter.git + ``` +2. Navigate to the project directory: + ```bash + cd netbird-slack-greeter + ``` +3. Build the project: + ```bash + cd bot + go build -o netbird-slack-greeter + ``` + +## Usage + +1. Set the environment variables `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN`. +2. Update your constants and channel IDs in `bot/main.go`. +3. Build the project: + ```bash + cd bot + go build -o netbird-slack-greeter + ``` +4. Execute the application: + ```bash + ./bot/netbird-slack-greeter + ``` + +## Environment Variables + +- `SLACK_APP_TOKEN`: Slack app-level token (must start with `xapp-`). +- `SLACK_BOT_TOKEN`: Slack bot token (must start with `xoxb-`). + +## Logging + +The project uses `logrus` for structured logging. It is configured to output logs in JSON format with timestamps and file locations for debugging. + +## Message Templates + +The application uses predefined templates for different types of messages: + +- `bugsIssuesEtcTemplate`: For general queries and bug reporting. +- `selfHostedTemplate`: For self-hosted deployment discussions and support. +- `newUserTemplate`: For welcoming new users to the NetBird Slack community. + +## Contributions + +Contributions are encouraged! For feature requests or bug reports, please file an issue or submit a pull request on the GitHub repository. + +## License + +[Specify License Here] + +## Support + +For support, join our Slack community or file an issue on the GitHub repository. diff --git a/bot/main.go b/bot/main.go new file mode 100644 index 0000000..0a0de07 --- /dev/null +++ b/bot/main.go @@ -0,0 +1,217 @@ +package main + +import ( + "bytes" + sl "log" + "os" + "path" + "runtime" + "strconv" + "strings" + "text/template" + "time" + + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" + + "github.com/slack-go/slack" + + log "github.com/sirupsen/logrus" +) + +const ( + bugsIssuesEtcTemplate = "Hello, <@{{.User}}>, and welcome to the <#{{.Channel}}> channel of NetBird Slack!☁️❗\n \n" + + "This channel is dedicated to queries on configuring your NetBird account, issues running NetBird clients, " + + "possible feature requests, and sharing use cases.\n\nFor prompt and effective assistance with issues, " + + "*please provide detailed information as outlined in our bug/issue reporting template*: " + + "https://docs.netbird.io/how-to/report-bug-issues#reporting-template\n\n*Note*: _We prioritize support for queries " + + "posted in the appropriate channel and those that follow our template guidelines._\n🌟 Your contributions are " + + "essential to us! Feel free to report bugs or suggest features via GitHub issues: " + + "https://github.com/netbirdio/netbird/issues" + selfHostedTemplate = "Hello, <@{{.User}}>, and welcome to the <#{{.Channel}}> channel of " + + "NetBird Slack! 🛠️\n\nThis channel is dedicated to discussions, support, and sharing issues specifically related " + + "to our self-hosted deployments.\n\nFor prompt and effective assistance, *please provide detailed information as outlined " + + "in our bug/issue reporting template*: https://docs.netbird.io/how-to/report-bug-issues#reporting-template\n\n*Note*: " + + "_We prioritize support for queries posted in the appropriate channel and those that follow our template guidelines._\n\n🌟 " + + "Your contributions are essential to us! Feel free to report bugs or suggest features via GitHub issues: " + + "https://github.com/netbirdio/netbird/issues" + newUserTemplate = "Hello, <@{{.User}}>, and welcome to NetBird's community Slack!🌐\n\n" + + "You will find updates and notifications from the NetBird team in our <#C028VPB34NB> " + + "channel.\n\n🔍 Are you encountering issues or need support?\n • " + + "For queries on how to configure your network or issues running netbird clients, " + + "join <#C02KHAE8VLZ>\n\n • For self-hosted issues, like setting up services or client communication, " + + "join <#C05T5K65X7U>\n\nFor prompt and effective assistance, *please provide detailed information as " + + "outlined in our bug/issue reporting template*: https://docs.netbird.io/how-to/report-bug-issues#reporting-template" +) + +type templateInput struct { + User string + Channel string +} + +func initLog() *log.Logger { + logFormatter := new(log.JSONFormatter) + logFormatter.TimestampFormat = time.RFC3339 // or RFC3339 + logFormatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) { + fileName := path.Base(frame.File) + ":" + strconv.Itoa(frame.Line) + return "", fileName + } + log.SetFormatter(logFormatter) + log.SetReportCaller(true) + log.SetOutput(os.Stdout) + log.SetLevel(log.TraceLevel) + + logger := log.New() + logger.SetFormatter(logFormatter) + logger.SetReportCaller(true) + logger.SetOutput(os.Stdout) + logger.SetLevel(log.TraceLevel) + return logger +} + +func main() { + logger := initLog() + appToken := os.Getenv("SLACK_APP_TOKEN") + if appToken == "" { + panic("SLACK_APP_TOKEN must be set.\n") + } + + if !strings.HasPrefix(appToken, "xapp-") { + panic("SLACK_APP_TOKEN must have the prefix \"xapp-\".") + } + + botToken := os.Getenv("SLACK_BOT_TOKEN") + if botToken == "" { + panic("SLACK_BOT_TOKEN must be set.\n") + } + + if !strings.HasPrefix(botToken, "xoxb-") { + panic("SLACK_BOT_TOKEN must have the prefix \"xoxb-\".") + } + + api := slack.New( + botToken, + slack.OptionDebug(false), + slack.OptionLog(sl.New(logger.Writer(), "api: ", sl.Lshortfile|sl.LstdFlags)), + slack.OptionAppLevelToken(appToken), + ) + + client := socketmode.New( + api, + socketmode.OptionDebug(false), + socketmode.OptionLog(sl.New(logger.Writer(), "socketmode: ", sl.Lshortfile|sl.LstdFlags)), + ) + + socketmodeHandler := socketmode.NewSocketmodeHandler(client) + + socketmodeHandler.Handle(socketmode.EventTypeConnecting, middlewareConnecting) + socketmodeHandler.Handle(socketmode.EventTypeConnectionError, middlewareConnectionError) + socketmodeHandler.Handle(socketmode.EventTypeConnected, middlewareConnected) + + socketmodeHandler.Handle(socketmode.EventTypeEventsAPI, getMiddlewareEventsAPI(api)) + + socketmodeHandler.RunEventLoop() +} + +func middlewareConnecting(evt *socketmode.Event, client *socketmode.Client) { + log.Info("connecting to Slack with Socket Mode...") +} + +func middlewareConnectionError(evt *socketmode.Event, client *socketmode.Client) { + log.Error("connection failed. Retrying later...") +} + +func middlewareConnected(evt *socketmode.Event, client *socketmode.Client) { + log.Info("connected to Slack with Socket Mode.") +} + +func getMiddlewareEventsAPI(api *slack.Client) func(evt *socketmode.Event, client *socketmode.Client) { + return func(evt *socketmode.Event, client *socketmode.Client) { + eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent) + if !ok { + log.Tracef("event ignored %+v", evt) + return + } + + log.Infof("event received: %+v\n", eventsAPIEvent) + + client.Ack(*evt.Request) + + switch eventsAPIEvent.Type { + case slackevents.CallbackEvent: + innerEvent := eventsAPIEvent.InnerEvent + switch ev := innerEvent.Data.(type) { + case *slackevents.MemberJoinedChannelEvent: + log.Debugf("user %q joined to channel %q", ev.User, ev.Channel) + templateText := getChannelTemplate(ev.Channel) + err := postMessage(api, true, ev.User, ev.Channel, templateText) + if err != nil { + log.Errorf("got an error when posting a message: %s\n", err) + } + case *slackevents.TeamJoinEvent: + log.Debugf("user %q joined to slack, its name is: %q", ev.User.ID, ev.User.Name) + channel, _, _, err := api.OpenConversation(&slack.OpenConversationParameters{Users: []string{ev.User.ID}}) + if err != nil { + log.Errorf("got an error when opening a conversation: %s\n", err) + return + } + err = postMessage(api, false, ev.User.ID, channel.ID, newUserTemplate) + if err != nil { + log.Errorf("got an error when posting a message: %s\n", err) + } + } + default: + } + } +} + +func getChannelTemplate(channel string) string { + switch channel { + case "C02KHAE8VLZ": + return bugsIssuesEtcTemplate + case "C05T5K65X7U": + return selfHostedTemplate + default: + return "" + } +} + +func postMessage(api *slack.Client, isChannelMSG bool, user, channel, templateText string) error { + if templateText == "" { + log.Infof("no template for channel %s", channel) + return nil + } + + text, err := parseText(templateInput{ + User: user, + Channel: channel, + }, templateText) + if err != nil { + return err + } + + options := []slack.MsgOption{slack.MsgOptionText(text, false), slack.MsgOptionAsUser(true)} + if isChannelMSG { + options = append([]slack.MsgOption{slack.MsgOptionPostEphemeral(user)}, options...) + } + + channelID, timestamp, err := api.PostMessage(channel, options...) + if err != nil { + return err + } + log.Infof("message successfully sent to channel %s at %s", channelID, timestamp) + return err +} + +func parseText(input templateInput, textTemplate string) (string, error) { + tmpl, err := template.New("msgs").Parse(textTemplate) + if err != nil { + return "", err + } + var tpl bytes.Buffer + err = tmpl.Execute(&tpl, input) + if err != nil { + return "", err + } + return tpl.String(), nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b408fc7 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module slack-nb-bot + +go 1.21 + +require ( + github.com/sirupsen/logrus v1.9.3 + github.com/slack-go/slack v0.12.3 +) + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..74e80ca --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +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/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +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/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slack-go/slack v0.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88= +github.com/slack-go/slack v0.12.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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=