From c6e2ab82933f1793801324e2497a81a8b2fbbe88 Mon Sep 17 00:00:00 2001 From: Matthieu Dolci Date: Fri, 13 Jul 2018 22:48:54 -0700 Subject: [PATCH] Allow to edit standup notes (#13) * initial commit * adding uuid and editing working * fix linting * some more linting fixing * some more cleaning * update sql request --- Gopkg.lock | 14 +- Gopkg.toml | 2 +- bot/common.go | 58 ++++ bot/handler_slack.go | 2 +- bot/listen_slack.go | 23 ++ bot/standup.go | 281 +++++++++++++++--- database/schema.sql | 23 ++ vendor/github.com/nlopes/slack/CHANGELOG.md | 6 + vendor/github.com/nlopes/slack/Gopkg.lock | 33 ++ vendor/github.com/nlopes/slack/Gopkg.toml | 13 + vendor/github.com/nlopes/slack/README.md | 18 +- vendor/github.com/nlopes/slack/admin.go | 2 + vendor/github.com/nlopes/slack/attachments.go | 1 + vendor/github.com/nlopes/slack/backoff.go | 2 +- vendor/github.com/nlopes/slack/channels.go | 28 +- vendor/github.com/nlopes/slack/chat.go | 133 ++++++--- .../github.com/nlopes/slack/conversation.go | 96 ++---- vendor/github.com/nlopes/slack/dnd.go | 6 +- vendor/github.com/nlopes/slack/files.go | 18 +- vendor/github.com/nlopes/slack/groups.go | 29 +- vendor/github.com/nlopes/slack/im.go | 9 +- vendor/github.com/nlopes/slack/info.go | 17 +- vendor/github.com/nlopes/slack/messages.go | 3 +- vendor/github.com/nlopes/slack/misc.go | 57 +++- vendor/github.com/nlopes/slack/pins.go | 24 +- vendor/github.com/nlopes/slack/reactions.go | 36 +-- vendor/github.com/nlopes/slack/rtm.go | 67 +++-- vendor/github.com/nlopes/slack/search.go | 25 +- vendor/github.com/nlopes/slack/slack.go | 14 +- vendor/github.com/nlopes/slack/slash.go | 26 +- vendor/github.com/nlopes/slack/stars.go | 24 +- vendor/github.com/nlopes/slack/users.go | 274 +++++++++++++---- vendor/github.com/nlopes/slack/websocket.go | 11 + .../nlopes/slack/websocket_managed_conn.go | 90 ++++-- .../github.com/nlopes/slack/websocket_misc.go | 21 +- vendor/github.com/satori/go.uuid/.travis.yml | 23 ++ vendor/github.com/satori/go.uuid/LICENSE | 20 ++ vendor/github.com/satori/go.uuid/README.md | 65 ++++ vendor/github.com/satori/go.uuid/codec.go | 206 +++++++++++++ vendor/github.com/satori/go.uuid/generator.go | 239 +++++++++++++++ vendor/github.com/satori/go.uuid/sql.go | 78 +++++ vendor/github.com/satori/go.uuid/uuid.go | 161 ++++++++++ vendor/golang.org/x/sys/unix/syscall_bsd.go | 8 +- .../x/sys/unix/syscall_dragonfly.go | 2 +- .../golang.org/x/sys/unix/syscall_freebsd.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 75 ++++- .../golang.org/x/sys/unix/syscall_solaris.go | 8 +- vendor/golang.org/x/sys/unix/syscall_unix.go | 4 +- .../golang.org/x/sys/unix/ztypes_linux_386.go | 8 + .../x/sys/unix/ztypes_linux_amd64.go | 8 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 8 + .../x/sys/unix/ztypes_linux_arm64.go | 8 + .../x/sys/unix/ztypes_linux_mips.go | 8 + .../x/sys/unix/ztypes_linux_mips64.go | 8 + .../x/sys/unix/ztypes_linux_mips64le.go | 8 + .../x/sys/unix/ztypes_linux_mipsle.go | 8 + .../x/sys/unix/ztypes_linux_ppc64.go | 8 + .../x/sys/unix/ztypes_linux_ppc64le.go | 8 + .../x/sys/unix/ztypes_linux_s390x.go | 8 + 59 files changed, 2024 insertions(+), 441 deletions(-) create mode 100644 bot/common.go create mode 100644 vendor/github.com/nlopes/slack/Gopkg.lock create mode 100644 vendor/github.com/nlopes/slack/Gopkg.toml create mode 100644 vendor/github.com/satori/go.uuid/.travis.yml create mode 100644 vendor/github.com/satori/go.uuid/LICENSE create mode 100644 vendor/github.com/satori/go.uuid/README.md create mode 100644 vendor/github.com/satori/go.uuid/codec.go create mode 100644 vendor/github.com/satori/go.uuid/generator.go create mode 100644 vendor/github.com/satori/go.uuid/sql.go create mode 100644 vendor/github.com/satori/go.uuid/uuid.go diff --git a/Gopkg.lock b/Gopkg.lock index bc191ba..cfd25e8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -35,10 +35,10 @@ revision = "9349a22bb5eb7862fcc435828c2252641a1cba95" [[projects]] + branch = "master" name = "github.com/nlopes/slack" packages = ["."] - revision = "8ab4d0b364ef1e9af5d102531da20d5ec902b6c4" - version = "v0.2.0" + revision = "13510604f06044c100aab2c446b0c0a28e4e8c18" [[projects]] name = "github.com/pkg/errors" @@ -46,6 +46,12 @@ revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + name = "github.com/satori/go.uuid" + packages = ["."] + revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" + version = "v1.2.0" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -59,11 +65,11 @@ "unix", "windows" ] - revision = "3c6ecd8f22c6f40fbeec94c000a069d7d87c7624" + revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "46c9aaa4ef28dd9bbb387595901e817e61708fc8832c3d81bf3a344d08bf6a23" + inputs-digest = "9032023e1a2f62b9efe1faef2ce96a5ec5f90bfd166dffe837b563f1e9fd2fad" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 8884d62..69447c0 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -29,8 +29,8 @@ name = "github.com/lib/pq" [[constraint]] + branch = "master" name = "github.com/nlopes/slack" - version = "0.2.0" [prune] go-tests = true diff --git a/bot/common.go b/bot/common.go new file mode 100644 index 0000000..6bf2f61 --- /dev/null +++ b/bot/common.go @@ -0,0 +1,58 @@ +package bot + +import ( + "database/sql" + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/matthieudolci/hatcher/database" + uuid "github.com/satori/go.uuid" +) + +func (s *Slack) rowExists(query string, args ...interface{}) bool { + + var exists bool + + err := database.DB.QueryRow(query, args...).Scan(&exists) + if err != nil && err != sql.ErrNoRows { + log.WithFields(log.Fields{ + "arg": args, + }).WithError(err).Error("error checking if row exists") + } + return exists +} + +func (s *Slack) queryRow(query string, args ...interface{}) error { + + var id int + + err := database.DB.QueryRow(query, args...).Scan(&id) + if err != nil && err != sql.ErrNoRows { + log.WithFields(log.Fields{ + "arg": args, + }).WithError(err).Error("error querying the row") + } + return nil +} + +func (s *Slack) queryUUID(query string, args ...interface{}) (string, error) { + + var uuid string + + err := database.DB.QueryRow(query, args...).Scan(&uuid) + if err != nil && err != sql.ErrNoRows { + log.WithFields(log.Fields{ + "arg": args, + }).WithError(err).Error("error querying the row") + } + + u := fmt.Sprint(uuid) + + return u, err +} + +func (s *Slack) createsUUID() string { + u := uuid.NewV4() + uuid := fmt.Sprint(u) + return uuid +} diff --git a/bot/handler_slack.go b/bot/handler_slack.go index 08eff9b..6e5ee81 100644 --- a/bot/handler_slack.go +++ b/bot/handler_slack.go @@ -11,7 +11,7 @@ import ( "github.com/nlopes/slack" ) -// Listen on /slack for answer from the questions asked in bot_setup.go +// Listen on /slack for answer from the questions asked in setup.go // and dispatch to the good functions func (s *Slack) slackPostHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { diff --git a/bot/listen_slack.go b/bot/listen_slack.go index 6570dd0..8214a38 100644 --- a/bot/listen_slack.go +++ b/bot/listen_slack.go @@ -64,6 +64,29 @@ func (s *Slack) run(ctx context.Context) { log.Info("Now listening for incoming messages...") for msg := range rtm.IncomingEvents { + switch x := msg.Data.(type) { + case *slack.MessageEvent: + if x.SubType == "message_changed" { + err := s.checkIfYesterdayMessageEdited(x) + if err != nil { + log.WithFields(log.Fields{ + "userid": x.User, + }).WithError(err).Error("Checking if yesterday standup was edited") + } + err = s.checkIfTodayMessageEdited(x) + if err != nil { + log.WithFields(log.Fields{ + "userid": x.User, + }).WithError(err).Error("Checking if yesterday standup was edited") + } + err = s.checkIfBlockerMessageEdited(x) + if err != nil { + log.WithFields(log.Fields{ + "userid": x.User, + }).WithError(err).Error("Checking if yesterday standup was edited") + } + } + } switch ev := msg.Data.(type) { case *slack.MessageEvent: if len(ev.User) == 0 { diff --git a/bot/standup.go b/bot/standup.go index f30f13b..b3f6c29 100644 --- a/bot/standup.go +++ b/bot/standup.go @@ -9,6 +9,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/matthieudolci/hatcher/database" "github.com/nlopes/slack" + uuid "github.com/satori/go.uuid" ) func (s *Slack) standupYesterday(ev *slack.MessageEvent) error { @@ -17,6 +18,11 @@ func (s *Slack) standupYesterday(ev *slack.MessageEvent) error { text = strings.TrimSpace(text) text = strings.ToLower(text) + uuid := s.createsUUID() + log.WithFields(log.Fields{ + "uuid": uuid, + }).Info("Standup uuid generated") + acceptedStandup := map[string]bool{ "standup": true, } @@ -93,13 +99,13 @@ func (s *Slack) standupYesterday(ev *slack.MessageEvent) error { break loop default: - s.standupYesterdayRegister(text, stamp, date, time, userid) + err = s.standupYesterdayRegister(text, stamp, date, time, userid, uuid) if err != nil { log.WithError(err).Error("Could not start standupYesterdayRegister") } log.Info("Starting standupYesterdayRegister") - err = s.standupToday(ev.Channel, ev.User) + err = s.standupToday(ev.Channel, ev.User, date, time, uuid) if err != nil { log.WithError(err).Error("Could not start standupToday") } @@ -116,6 +122,12 @@ func (s *Slack) standupYesterday(ev *slack.MessageEvent) error { func (s *Slack) standupYesterdayScheduled(userid string) error { + u := uuid.NewV4() + uuid := fmt.Sprint(u) + log.WithFields(log.Fields{ + "uuid": uuid, + }).Info("Standup uuid generated") + _, _, channelid, _ := s.Client.OpenIMChannel(userid) attachment := slack.Attachment{ @@ -190,13 +202,13 @@ loop: break loop default: - s.standupYesterdayRegister(text, stamp, date, time, userid) + err = s.standupYesterdayRegister(text, stamp, date, time, userid, uuid) if err != nil { log.WithError(err).Error("Could not start standupYesterdayRegister") } log.Info("Starting standupYesterdayRegister") - err = s.standupToday(channelid, userid) + err = s.standupToday(channelid, userid, date, time, uuid) if err != nil { log.WithError(err).Error("Could not start standupToday") } @@ -209,7 +221,7 @@ loop: return nil } -func (s *Slack) standupYesterdayRegister(response, timestamp, date, time, userid string) error { +func (s *Slack) standupYesterdayRegister(response, timestamp, date, time, userid, uuid string) error { log.Info("Starting import in database of standupYesterday result") @@ -218,8 +230,8 @@ func (s *Slack) standupYesterdayRegister(response, timestamp, date, time, userid sqlWrite := ` INSERT INTO hatcher.standupyesterday - (response, timestamp, date, time, userid) - VALUES ($1, $2, $3, $4, $5) + (response, timestamp, date, time, userid, uuid) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id; ` @@ -229,7 +241,8 @@ func (s *Slack) standupYesterdayRegister(response, timestamp, date, time, userid timestamp, date, time, - userid).Scan(&id) + userid, + uuid).Scan(&id) if err != nil { log.WithError(err).Error("Couldn't insert in the database the result of standupYesterday") } @@ -237,7 +250,7 @@ func (s *Slack) standupYesterdayRegister(response, timestamp, date, time, userid return nil } -func (s *Slack) standupToday(channelid, userid string) error { +func (s *Slack) standupToday(channelid, userid, date, times, uuid string) error { attachment := slack.Attachment{ Text: "What are you doing today?", @@ -292,10 +305,6 @@ loop: } if len(message) > 0 { text := history.Messages[0].Msg.Text - t := time.Now().Local().Format("2006-01-02") - t2 := time.Now().Local().Format("15:04:05") - date := fmt.Sprintf(t) - time := fmt.Sprintf(t2) userid := history.Messages[0].Msg.User stamp := history.Messages[0].Msg.Timestamp switch text { @@ -308,13 +317,13 @@ loop: break loop default: - err := s.standupTodayRegister(text, stamp, date, time, userid) + err := s.standupTodayRegister(text, stamp, date, times, userid, uuid) if err != nil { log.WithError(err).Error("Could not start standupTodayRegister") } log.Info("Starting standupTodayRegister") - err = s.standupBlocker(channelid, userid) + err = s.standupBlocker(channelid, userid, date, times, uuid) if err != nil { log.WithError(err).Error("Could not start standupBlocker") } @@ -328,7 +337,7 @@ loop: return nil } -func (s *Slack) standupTodayRegister(response, timestamp, date, time, userid string) error { +func (s *Slack) standupTodayRegister(response, timestamp, date, time, userid, uuid string) error { log.Info("Starting import in database of standupToday result") @@ -337,8 +346,8 @@ func (s *Slack) standupTodayRegister(response, timestamp, date, time, userid str sqlWrite := ` INSERT INTO hatcher.standuptoday - (response, timestamp, date, time, userid) - VALUES ($1, $2, $3, $4, $5) + (response, timestamp, date, time, userid, uuid) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id; ` @@ -348,7 +357,8 @@ func (s *Slack) standupTodayRegister(response, timestamp, date, time, userid str timestamp, date, time, - userid).Scan(&id) + userid, + uuid).Scan(&id) if err != nil { log.WithError(err).Error("Couldn't insert in the database the result of standupToday") } @@ -357,7 +367,7 @@ func (s *Slack) standupTodayRegister(response, timestamp, date, time, userid str return nil } -func (s *Slack) standupBlocker(channelid, userid string) error { +func (s *Slack) standupBlocker(channelid, userid, date, times, uuid string) error { attachment := slack.Attachment{ Text: "Do you have any blockers?", @@ -412,10 +422,6 @@ loop: } if len(message) > 0 { text := history.Messages[0].Msg.Text - t := time.Now().Local().Format("2006-01-02") - t2 := time.Now().Local().Format("15:04:05") - date := fmt.Sprintf(t) - time := fmt.Sprintf(t2) userid := history.Messages[0].Msg.User stamp := history.Messages[0].Msg.Timestamp switch text { @@ -428,13 +434,13 @@ loop: break loop default: - err := s.standupBlockerRegister(text, stamp, date, time, userid) + err := s.standupBlockerRegister(text, stamp, date, times, userid, uuid) if err != nil { log.WithError(err).Error("Could not start standupBlockerRegister") } log.Info("Started standupBlockerRegister") - err = s.standupDone(channelid, userid, date) + err = s.standupDone(channelid, userid, date, times, uuid) if err != nil { log.WithError(err).Error("Could not start standupDone") } @@ -448,7 +454,7 @@ loop: return nil } -func (s *Slack) standupBlockerRegister(response, timestamp, date, time, userid string) error { +func (s *Slack) standupBlockerRegister(response, timestamp, date, time, userid, uuid string) error { log.Info("Starting import in database of standupBlocker result") @@ -457,8 +463,8 @@ func (s *Slack) standupBlockerRegister(response, timestamp, date, time, userid s sqlWrite := ` INSERT INTO hatcher.standupblocker - (response, timestamp, date, time, userid) - VALUES ($1, $2, $3, $4, $5) + (response, timestamp, date, time, userid, uuid) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id; ` @@ -468,7 +474,8 @@ func (s *Slack) standupBlockerRegister(response, timestamp, date, time, userid s timestamp, date, time, - userid).Scan(&id) + userid, + uuid).Scan(&id) if err != nil { log.WithError(err).Error("Couldn't insert in the database the result of standupBlocker") } @@ -521,7 +528,7 @@ func (s *Slack) standupCancelTimeout(channelid string) error { return nil } -func (s *Slack) standupDone(channelid, userid, date string) error { +func (s *Slack) standupDone(channelid, userid, date, time, uuid string) error { attachment := slack.Attachment{ Text: "Standup Done! Thanks and see you tomorrow :smiley:", @@ -540,7 +547,7 @@ func (s *Slack) standupDone(channelid, userid, date string) error { } log.Info("Posted standup done message") - err = s.postStandupResults(userid, date) + err = s.postStandupResults(userid, date, time, uuid) if err != nil { log.WithError(err).Error("Could not start postStandup") } @@ -549,7 +556,7 @@ func (s *Slack) standupDone(channelid, userid, date string) error { return nil } -func (s *Slack) postStandupResults(userid, date string) error { +func (s *Slack) postStandupResults(userid, date, time, uuid string) error { rows, err := database.DB.Query("SELECT userid, displayname, standup_channel FROM hatcher.users WHERE userid = $1;", userid) if err != nil { @@ -573,9 +580,8 @@ func (s *Slack) postStandupResults(userid, date string) error { } attachment := slack.Attachment{ - Pretext: fmt.Sprintf("*%s* posted a daily standup note", displayname), Title: "What did you do yesterday?", - Text: fmt.Sprintf("%s", responseYesterday), + Text: responseYesterday, Color: "#2896b7", CallbackID: fmt.Sprintf("resultsStandupYesterday_%s", userid), } @@ -583,14 +589,14 @@ func (s *Slack) postStandupResults(userid, date string) error { attachment2 := slack.Attachment{ Title: "What are you doing today?", Color: "#41aa3f", - Text: fmt.Sprintf("%s", responseToday), + Text: responseToday, CallbackID: fmt.Sprintf("resultsStandupToday_%s", userid), } attachment3 := slack.Attachment{ Title: "Do you have any blockers?", Color: "#f91b1b", - Text: fmt.Sprintf("%s", responseBlocker), + Text: responseBlocker, CallbackID: fmt.Sprintf("resultsStandupBlocker_%s", userid), } @@ -601,11 +607,26 @@ func (s *Slack) postStandupResults(userid, date string) error { attachment3, }, } - _, _, err := s.Client.PostMessage(standupChannel, "", params) + + text := fmt.Sprintf("%s posted a daily standup note", displayname) + _, respTimestamp, err := s.Client.PostMessage( + standupChannel, + text, + params) if err != nil { log.WithError(err).Error("Failed to post standup results") } - log.Info("Standup posted") + log.WithFields(log.Fields{ + "timestamp": respTimestamp, + }).Info("Standup results posted") + + err = s.queryRow("INSERT INTO hatcher.standupresults (timestamp, date, time, uuid) VALUES ($1, $2, $3, $4)", respTimestamp, date, time, uuid) + if err != nil { + log.WithError(err).Error("Could not edit the standup result timestamp row") + } + log.WithFields(log.Fields{ + "timestamp": respTimestamp, + }).Info("The standup result timestamp row was edited") } return nil @@ -671,3 +692,185 @@ func (s *Slack) standupResultsBlocker(userid, date, standupChannel string) (resp } return response } + +func (s *Slack) checkIfYesterdayMessageEdited(ev *slack.MessageEvent) error { + + timestamp := ev.SubMessage.Timestamp + text := ev.SubMessage.Text + userid := ev.SubMessage.User + + if s.rowExists("SELECT exists (SELECT timestamp FROM hatcher.standupyesterday WHERE timestamp=$1)", timestamp) { + err := s.queryRow("UPDATE hatcher.standupyesterday SET response=$2 WHERE timestamp=$1", timestamp, text) + if err != nil { + log.WithError(err).Error("Could not edit the yesterday standup row") + } + log.WithFields(log.Fields{ + "timestamp": timestamp, + }).Info("The yesterday standup row was edited") + + var standupUUID string + + standupUUID, err = s.queryUUID("SELECT uuid FROM hatcher.standupyesterday WHERE timestamp=$1", timestamp) + if err != nil { + log.WithError(err).Error("Could get the standup uuid") + } + log.WithFields(log.Fields{ + "uuid": standupUUID, + "timestamp": timestamp, + }).Info("The standup uuid was retrieve") + + err = s.updateStandupResults(userid, standupUUID) + if err != nil { + log.WithError(err).Error("Could not start the standup update for yesterday notes") + } + + } else { + log.Println("The row doesn't exist") + } + return nil +} + +func (s *Slack) checkIfTodayMessageEdited(ev *slack.MessageEvent) error { + + timestamp := ev.SubMessage.Timestamp + text := ev.SubMessage.Text + userid := ev.SubMessage.User + + if s.rowExists("SELECT exists (SELECT timestamp FROM hatcher.standuptoday WHERE timestamp=$1)", timestamp) { + err := s.queryRow("UPDATE hatcher.standuptoday SET response=$2 WHERE timestamp=$1", timestamp, text) + if err != nil { + log.WithError(err).Error("Could not edit the today standup row") + } + log.WithFields(log.Fields{ + "timestamp": timestamp, + }).Info("The today standup row was edited") + + var standupUUID string + + standupUUID, err = s.queryUUID("SELECT uuid FROM hatcher.standuptoday WHERE timestamp=$1", timestamp) + if err != nil { + log.WithError(err).Error("Could get the standup uuid") + } + log.WithFields(log.Fields{ + "uuid": standupUUID, + "timestamp": timestamp, + }).Info("The standup uuid was retrieve") + + err = s.updateStandupResults(userid, standupUUID) + if err != nil { + log.WithError(err).Error("Could not start the standup update for Today notes") + } + + } else { + log.Println("The row doesn't exist") + } + return nil +} + +func (s *Slack) checkIfBlockerMessageEdited(ev *slack.MessageEvent) error { + + timestamp := ev.SubMessage.Timestamp + text := ev.SubMessage.Text + userid := ev.SubMessage.User + + if s.rowExists("SELECT exists (SELECT timestamp FROM hatcher.standupblocker WHERE timestamp=$1)", timestamp) { + err := s.queryRow("UPDATE hatcher.standupblocker SET response=$2 WHERE timestamp=$1", timestamp, text) + if err != nil { + log.WithError(err).Error("Could not edit the blocker standup row") + } + log.WithFields(log.Fields{ + "timestamp": timestamp, + }).Info("The blocker standup row was edited") + + var standupUUID string + + standupUUID, err = s.queryUUID("SELECT uuid FROM hatcher.standupblocker WHERE timestamp=$1", timestamp) + if err != nil { + log.WithError(err).Error("Could get the standup uuid") + } + log.WithFields(log.Fields{ + "uuid": standupUUID, + "timestamp": timestamp, + }).Info("The standup uuid was retrieve") + + err = s.updateStandupResults(userid, standupUUID) + if err != nil { + log.WithError(err).Error("Could not start the standup update for blocker") + } + + } else { + log.Println("The row doesn't exist") + } + return nil +} + +func (s *Slack) updateStandupResults(userid, uuid string) error { + + rows, err := database.DB.Query("SELECT userid, displayname, standup_channel FROM hatcher.users WHERE userid = $1;", userid) + if err != nil { + if err == sql.ErrNoRows { + log.WithError(err).Error("There is no results") + } + } + + defer rows.Close() + for rows.Next() { + + var displayname string + var standupChannel string + var timestamp string + var date string + + err := database.DB.QueryRow("SELECT timestamp, date FROM hatcher.standupresults WHERE uuid=$1", uuid).Scan(×tamp, &date) + if err != nil && err != sql.ErrNoRows { + log.WithError(err).Error("Impossible to get the standup result uuid") + } + + responseYesterday := s.standupResultsYesterday(userid, date, standupChannel) + responseToday := s.standupResultsToday(userid, date, standupChannel) + responseBlocker := s.standupResultsBlocker(userid, date, standupChannel) + + err = rows.Scan(&userid, &displayname, &standupChannel) + if err != nil { + log.WithError(err).Error("During the scan") + } + + attachment := slack.Attachment{ + Title: "What did you do yesterday?", + Color: "#2896b7", + Text: responseYesterday, + CallbackID: fmt.Sprintf("resultsStandupYesterday_%s", userid), + } + + attachment2 := slack.Attachment{ + Title: "What are you doing today?", + Color: "#41aa3f", + Text: responseToday, + CallbackID: fmt.Sprintf("resultsStandupToday_%s", userid), + } + + attachment3 := slack.Attachment{ + Title: "Do you have any blockers?", + Color: "#f91b1b", + Text: responseBlocker, + CallbackID: fmt.Sprintf("resultsStandupBlocker_%s", userid), + } + + text := fmt.Sprintf("%s posted a daily standup note", displayname) + _, _, _, err = s.Client.SendMessage( + standupChannel, + slack.MsgOptionText(text, false), + slack.MsgOptionUpdate(timestamp), + slack.MsgOptionAttachments( + attachment, + attachment2, + attachment3, + ), + ) + if err != nil { + log.WithError(err).Error("Failed to post updated standup results") + } + log.Info("Updated standup results posted") + } + return nil +} diff --git a/database/schema.sql b/database/schema.sql index 037df47..688ef2f 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -40,6 +40,7 @@ CREATE TABLE hatcher.standupyesterday ( date text, userid text, time text, + uuid text, id integer NOT NULL ); @@ -59,6 +60,7 @@ CREATE TABLE hatcher.standuptoday ( date text, userid text, time text, + uuid text, id integer NOT NULL ); @@ -78,6 +80,7 @@ CREATE TABLE hatcher.standupblocker ( date text, userid text, time text, + uuid text, id integer NOT NULL ); @@ -114,6 +117,24 @@ CREATE SEQUENCE hatcher.usersid_seq ALTER SEQUENCE hatcher.usersid_seq OWNED BY hatcher.users.id; +CREATE TABLE hatcher.standupresults ( + id integer NOT NULL, + date text, + time text, + uuid text, + timestamp text +); + +CREATE SEQUENCE hatcher.standupresults_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE hatcher.standupresults_seq OWNED BY hatcher.standupresults.id; + ALTER TABLE ONLY hatcher.happiness ALTER COLUMN id SET DEFAULT nextval('hatcher.happinessid_seq'::regclass); ALTER TABLE ONLY hatcher.users ALTER COLUMN id SET DEFAULT nextval('hatcher.usersid_seq'::regclass); @@ -124,6 +145,8 @@ ALTER TABLE ONLY hatcher.standuptoday ALTER COLUMN id SET DEFAULT nextval('hatch ALTER TABLE ONLY hatcher.standupblocker ALTER COLUMN id SET DEFAULT nextval('hatcher.standupblocker_seq'::regclass); +ALTER TABLE ONLY hatcher.standupresults ALTER COLUMN id SET DEFAULT nextval('hatcher.standupresults_seq'::regclass); + ALTER TABLE ONLY hatcher.users ADD CONSTRAINT users_pkey PRIMARY KEY (userid); diff --git a/vendor/github.com/nlopes/slack/CHANGELOG.md b/vendor/github.com/nlopes/slack/CHANGELOG.md index 8c4772d..53cf943 100644 --- a/vendor/github.com/nlopes/slack/CHANGELOG.md +++ b/vendor/github.com/nlopes/slack/CHANGELOG.md @@ -1,3 +1,9 @@ +### v0.2.0 - Feb 10, 2018 + +Release adds a bunch of functionality and improvements, mainly to give people a recent version to vendor against. + +Please check [0.2.0](https://github.com/nlopes/slack/releases/tag/v0.2.0) + ### v0.1.0 - May 28, 2017 This is released before adding context support. diff --git a/vendor/github.com/nlopes/slack/Gopkg.lock b/vendor/github.com/nlopes/slack/Gopkg.lock new file mode 100644 index 0000000..5cc0520 --- /dev/null +++ b/vendor/github.com/nlopes/slack/Gopkg.lock @@ -0,0 +1,33 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/gorilla/websocket" + packages = ["."] + revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" + version = "v1.2.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "888307bf47ee004aaaa4c45e6139929b4984f2253e48e382246bfb8c66f3cd65" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/nlopes/slack/Gopkg.toml b/vendor/github.com/nlopes/slack/Gopkg.toml new file mode 100644 index 0000000..5271019 --- /dev/null +++ b/vendor/github.com/nlopes/slack/Gopkg.toml @@ -0,0 +1,13 @@ +ignored = ["github.com/lusis/slack-test"] + +[[constraint]] + name = "github.com/gorilla/websocket" + version = "1.2.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.1" + +[prune] + go-tests = true + unused-packages = true diff --git a/vendor/github.com/nlopes/slack/README.md b/vendor/github.com/nlopes/slack/README.md index 953b9d8..849e8bd 100644 --- a/vendor/github.com/nlopes/slack/README.md +++ b/vendor/github.com/nlopes/slack/README.md @@ -7,19 +7,20 @@ This library supports most if not all of the `api.slack.com` REST calls, as well as the Real-Time Messaging protocol over websocket, in a fully managed way. + + ## Change log +Support for the EventsAPI has recently been added. It is still in its early stages but nearly all events have been added and tested (except for those events in [Developer Preview](https://api.slack.com/slack-apps-preview) mode). API stability for events is not promised at this time. -### v0.1.0 - May 28, 2017 +### v0.2.0 - Feb 10, 2018 -This is released before adding context support. -As the used context package is the one from Go 1.7 this will be the last -compatible with Go < 1.7. +Release adds a bunch of functionality and improvements, mainly to give people a recent version to vendor against. -Please check [0.1.0](https://github.com/nlopes/slack/releases/tag/v0.1.0) +Please check [0.2.0](https://github.com/nlopes/slack/releases/tag/v0.2.0) ### CHANGELOG.md -As of this version a [CHANGELOG.md](https://github.com/nlopes/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates. + [CHANGELOG.md](https://github.com/nlopes/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates. ## Installing @@ -79,6 +80,11 @@ func main() { See https://github.com/nlopes/slack/blob/master/examples/websocket/websocket.go +## Minimal EventsAPI usage: + +See https://github.com/nlopes/slack/blob/master/examples/eventsapi/events.go + + ## Contributing You are more than welcome to contribute to this project. Fork and diff --git a/vendor/github.com/nlopes/slack/admin.go b/vendor/github.com/nlopes/slack/admin.go index 4a7e0b1..a2aa7e5 100644 --- a/vendor/github.com/nlopes/slack/admin.go +++ b/vendor/github.com/nlopes/slack/admin.go @@ -62,6 +62,7 @@ func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, fi "last_name": {lastName}, "ultra_restricted": {"1"}, "token": {api.token}, + "resend": {"true"}, "set_active": {"true"}, "_attempts": {"1"}, } @@ -88,6 +89,7 @@ func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channe "last_name": {lastName}, "restricted": {"1"}, "token": {api.token}, + "resend": {"true"}, "set_active": {"true"}, "_attempts": {"1"}, } diff --git a/vendor/github.com/nlopes/slack/attachments.go b/vendor/github.com/nlopes/slack/attachments.go index d2b8b23..326fc01 100644 --- a/vendor/github.com/nlopes/slack/attachments.go +++ b/vendor/github.com/nlopes/slack/attachments.go @@ -78,6 +78,7 @@ type Attachment struct { CallbackID string `json:"callback_id,omitempty"` ID int `json:"id,omitempty"` + AuthorID string `json:"author_id,omitempty"` AuthorName string `json:"author_name,omitempty"` AuthorSubname string `json:"author_subname,omitempty"` AuthorLink string `json:"author_link,omitempty"` diff --git a/vendor/github.com/nlopes/slack/backoff.go b/vendor/github.com/nlopes/slack/backoff.go index e555a1a..197bce2 100644 --- a/vendor/github.com/nlopes/slack/backoff.go +++ b/vendor/github.com/nlopes/slack/backoff.go @@ -38,7 +38,7 @@ func (b *backoff) Duration() time.Duration { } //calculate this duration dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts)) - if b.Jitter == true { + if b.Jitter { dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min) } //cap! diff --git a/vendor/github.com/nlopes/slack/channels.go b/vendor/github.com/nlopes/slack/channels.go index b16e19f..6204315 100644 --- a/vendor/github.com/nlopes/slack/channels.go +++ b/vendor/github.com/nlopes/slack/channels.go @@ -52,11 +52,8 @@ func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) "channel": {channelID}, } - if _, err = channelRequest(ctx, api.httpclient, "channels.archive", values, api.debug); err != nil { - return err - } - - return nil + _, err = channelRequest(ctx, api.httpclient, "channels.archive", values, api.debug) + return err } // UnarchiveChannel unarchives the given channel @@ -73,11 +70,8 @@ func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string "channel": {channelID}, } - if _, err = channelRequest(ctx, api.httpclient, "channels.unarchive", values, api.debug); err != nil { - return err - } - - return nil + _, err = channelRequest(ctx, api.httpclient, "channels.unarchive", values, api.debug) + return err } // CreateChannel creates a channel with the given name and returns a *Channel @@ -247,11 +241,8 @@ func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, us "user": {user}, } - if _, err = channelRequest(ctx, api.httpclient, "channels.kick", values, api.debug); err != nil { - return err - } - - return nil + _, err = channelRequest(ctx, api.httpclient, "channels.kick", values, api.debug) + return err } // GetChannels retrieves all the channels @@ -297,11 +288,8 @@ func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts "ts": {ts}, } - if _, err = channelRequest(ctx, api.httpclient, "channels.mark", values, api.debug); err != nil { - return err - } - - return nil + _, err = channelRequest(ctx, api.httpclient, "channels.mark", values, api.debug) + return err } // RenameChannel renames a given channel diff --git a/vendor/github.com/nlopes/slack/chat.go b/vendor/github.com/nlopes/slack/chat.go index fae416b..12c7dd8 100644 --- a/vendor/github.com/nlopes/slack/chat.go +++ b/vendor/github.com/nlopes/slack/chat.go @@ -3,7 +3,6 @@ package slack import ( "context" "encoding/json" - "errors" "net/url" "strings" ) @@ -24,15 +23,26 @@ const ( ) type chatResponseFull struct { - Channel string `json:"channel"` - Timestamp string `json:"ts"` - Text string `json:"text"` + Channel string `json:"channel"` + Timestamp string `json:"ts"` //Regualr message timestamp + MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp + Text string `json:"text"` SlackResponse } +// getMessageTimestamp will inspect the `chatResponseFull` to ruturn a timestamp value +// in `chat.postMessage` its under `ts` +// in `chat.postEphemeral` its under `message_ts` +func (c chatResponseFull) getMessageTimestamp() string { + if len(c.Timestamp) > 0 { + return c.Timestamp + } + return c.MessageTimeStamp +} + // PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request type PostMessageParameters struct { - Username string `json:"user_name"` + Username string `json:"username"` AsUser bool `json:"as_user"` Parse string `json:"parse"` ThreadTimestamp string `json:"thread_ts"` @@ -112,11 +122,10 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string, // PostEphemeral sends an ephemeral message to a user in a channel. // Message is escaped by default according to https://api.slack.com/docs/formatting // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. -func (api *Client) PostEphemeral(channel, userID string, options ...MsgOption) (string, error) { - options = append(options, MsgOptionPostEphemeral()) +func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption) (string, error) { return api.PostEphemeralContext( context.Background(), - channel, + channelID, userID, options..., ) @@ -124,30 +133,19 @@ func (api *Client) PostEphemeral(channel, userID string, options ...MsgOption) ( // PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context // For more details, see PostEphemeral documentation -func (api *Client) PostEphemeralContext(ctx context.Context, channel, userID string, options ...MsgOption) (string, error) { - path, values, err := ApplyMsgOptions(api.token, channel, options...) - if err != nil { - return "", err - } - - values.Add("user", userID) - - response, err := chatRequest(ctx, api.httpclient, path, values, api.debug) - if err != nil { - return "", err - } - - return response.Timestamp, nil +func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) { + _, timestamp, _, err = api.SendMessageContext(ctx, channelID, append(options, MsgOptionPostEphemeral2(userID))...) + return timestamp, err } // UpdateMessage updates a message in a channel -func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) { - return api.UpdateMessageContext(context.Background(), channel, timestamp, text) +func (api *Client) UpdateMessage(channelID, timestamp, text string) (string, string, string, error) { + return api.UpdateMessageContext(context.Background(), channelID, timestamp, text) } // UpdateMessageContext updates a message in a channel -func (api *Client) UpdateMessageContext(ctx context.Context, channel, timestamp, text string) (string, string, string, error) { - return api.SendMessageContext(ctx, channel, MsgOptionUpdate(timestamp), MsgOptionText(text, true)) +func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp, text string) (string, string, string, error) { + return api.SendMessageContext(ctx, channelID, MsgOptionUpdate(timestamp), MsgOptionText(text, true)) } // SendMessage more flexible method for configuring messages. @@ -156,22 +154,30 @@ func (api *Client) SendMessage(channel string, options ...MsgOption) (string, st } // SendMessageContext more flexible method for configuring messages with a custom context. -func (api *Client) SendMessageContext(ctx context.Context, channel string, options ...MsgOption) (string, string, string, error) { - channel, values, err := ApplyMsgOptions(api.token, channel, options...) - if err != nil { +func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (channel string, timestamp string, text string, err error) { + var ( + config sendConfig + response chatResponseFull + ) + + if config, err = applyMsgOptions(api.token, channelID, options...); err != nil { return "", "", "", err } - response, err := chatRequest(ctx, api.httpclient, channel, values, api.debug) - if err != nil { + if err = post(ctx, api.httpclient, string(config.mode), config.values, &response, api.debug); err != nil { return "", "", "", err } - return response.Channel, response.Timestamp, response.Text, nil + return response.Channel, response.getMessageTimestamp(), response.Text, response.Err() } // ApplyMsgOptions utility function for debugging/testing chat requests. func ApplyMsgOptions(token, channel string, options ...MsgOption) (string, url.Values, error) { + config, err := applyMsgOptions(token, channel, options...) + return string(config.mode), config.values, err +} + +func applyMsgOptions(token, channel string, options ...MsgOption) (sendConfig, error) { config := sendConfig{ mode: chatPostMessage, values: url.Values{ @@ -182,11 +188,11 @@ func ApplyMsgOptions(token, channel string, options ...MsgOption) (string, url.V for _, opt := range options { if err := opt(&config); err != nil { - return string(config.mode), config.values, err + return config, err } } - return string(config.mode), config.values, nil + return config, nil } func escapeMessage(message string) string { @@ -194,18 +200,6 @@ func escapeMessage(message string) string { return replacer.Replace(message) } -func chatRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*chatResponseFull, error) { - response := &chatResponseFull{} - err := post(ctx, client, path, values, response, debug) - if err != nil { - return nil, err - } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response, nil -} - type sendMode string const ( @@ -232,7 +226,8 @@ func MsgOptionPost() MsgOption { } } -// MsgOptionPostEphemeral posts an ephemeral message +// MsgOptionPostEphemeral - DEPRECATED: use MsgOptionPostEphemeral2 +// posts an ephemeral message. func MsgOptionPostEphemeral() MsgOption { return func(config *sendConfig) error { config.mode = chatPostEphemeral @@ -241,6 +236,17 @@ func MsgOptionPostEphemeral() MsgOption { } } +// MsgOptionPostEphemeral2 - posts an ephemeral message to the provided user. +func MsgOptionPostEphemeral2(userID string) MsgOption { + return func(config *sendConfig) error { + config.mode = chatPostEphemeral + MsgOptionUser(userID)(config) + config.values.Del("ts") + + return nil + } +} + // MsgOptionUpdate updates a message based on the timestamp. func MsgOptionUpdate(timestamp string) MsgOption { return func(config *sendConfig) error { @@ -269,6 +275,14 @@ func MsgOptionAsUser(b bool) MsgOption { } } +// MsgOptionUser set the user for the message. +func MsgOptionUser(userID string) MsgOption { + return func(config *sendConfig) error { + config.values.Set("user", userID) + return nil + } +} + // MsgOptionText provide the text for the message, optionally escape the provided // text. func MsgOptionText(text string, escape bool) MsgOption { @@ -328,11 +342,32 @@ func MsgOptionDisableMarkdown() MsgOption { } } +// this function combines multiple options into a single option. +func MsgOptionCompose(options ...MsgOption) MsgOption { + return func(c *sendConfig) error { + for _, opt := range options { + if err := opt(c); err != nil { + return err + } + } + return nil + } +} + +func MsgOptionParse(b bool) MsgOption { + return func(c *sendConfig) error { + var v string + if b { v = "1" } else { v = "0" } + c.values.Set("parse", v) + return nil + } +} + // MsgOptionPostMessageParameters maintain backwards compatibility. func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { return func(config *sendConfig) error { if params.Username != DEFAULT_MESSAGE_USERNAME { - config.values.Set("username", string(params.Username)) + config.values.Set("username", params.Username) } // chat.postEphemeral support @@ -344,7 +379,7 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { MsgOptionAsUser(params.AsUser)(config) if params.Parse != DEFAULT_MESSAGE_PARSE { - config.values.Set("parse", string(params.Parse)) + config.values.Set("parse", params.Parse) } if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES { config.values.Set("link_names", "1") diff --git a/vendor/github.com/nlopes/slack/conversation.go b/vendor/github.com/nlopes/slack/conversation.go index 26ee292..b2dcc1d 100644 --- a/vendor/github.com/nlopes/slack/conversation.go +++ b/vendor/github.com/nlopes/slack/conversation.go @@ -83,7 +83,7 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge values.Add("cursor", params.Cursor) } if params.Limit != 0 { - values.Add("limit", string(params.Limit)) + values.Add("limit", strconv.Itoa(params.Limit)) } response := struct { Members []string `json:"members"` @@ -116,10 +116,8 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str if err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // UnArchiveConversation reverses conversation archival @@ -138,10 +136,8 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s if err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // SetTopicOfConversation sets the topic for a conversation @@ -164,10 +160,8 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response.Channel, nil + + return response.Channel, response.Err() } // SetPurposeOfConversation sets the purpose for a conversation @@ -190,10 +184,8 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response.Channel, nil + + return response.Channel, response.Err() } // RenameConversation renames a conversation @@ -216,10 +208,8 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response.Channel, nil + + return response.Channel, response.Err() } // InviteUsersToConversation invites users to a channel @@ -242,10 +232,8 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response.Channel, nil + + return response.Channel, response.Err() } // KickUserFromConversation removes a user from a conversation @@ -265,10 +253,8 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI if err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // CloseConversation closes a direct message or multi-person direct message @@ -292,10 +278,8 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin if err != nil { return false, false, err } - if !response.Ok { - return false, false, errors.New(response.Error) - } - return response.NoOp, response.AlreadyClosed, nil + + return response.NoOp, response.AlreadyClosed, response.Err() } // CreateConversation initiates a public or private channel-based conversation @@ -315,10 +299,8 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return &response.Channel, nil + + return &response.Channel, response.Err() } // GetConversationInfo retrieves information about a conversation @@ -338,10 +320,8 @@ func (api *Client) GetConversationInfoContext(ctx context.Context, channelID str if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return &response.Channel, nil + + return &response.Channel, response.Err() } // LeaveConversation leaves a conversation @@ -357,11 +337,7 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin } response, err := channelRequest(ctx, api.httpclient, "conversations.leave", values, api.debug) - if err != nil { - return false, err - } - - return response.NotInChannel, nil + return response.NotInChannel, err } type GetConversationRepliesParameters struct { @@ -393,7 +369,7 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge values.Add("latest", params.Latest) } if params.Limit != 0 { - values.Add("limit", string(params.Limit)) + values.Add("limit", strconv.Itoa(params.Limit)) } if params.Oldest != "" { values.Add("oldest", params.Oldest) @@ -416,10 +392,8 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge if err != nil { return nil, false, "", err } - if !response.Ok { - return nil, false, "", errors.New(response.Error) - } - return response.Messages, response.HasMore, response.ResponseMetaData.NextCursor, nil + + return response.Messages, response.HasMore, response.ResponseMetaData.NextCursor, response.Err() } type GetConversationsParameters struct { @@ -444,7 +418,7 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve values.Add("cursor", params.Cursor) } if params.Limit != 0 { - values.Add("limit", string(params.Limit)) + values.Add("limit", strconv.Itoa(params.Limit)) } if params.Types != nil { values.Add("types", strings.Join(params.Types, ",")) @@ -458,10 +432,8 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve if err != nil { return nil, "", err } - if !response.Ok { - return nil, "", errors.New(response.Error) - } - return response.Channels, response.ResponseMetaData.NextCursor, nil + + return response.Channels, response.ResponseMetaData.NextCursor, response.Err() } type OpenConversationParameters struct { @@ -497,10 +469,8 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv if err != nil { return nil, false, false, err } - if !response.Ok { - return nil, false, false, errors.New(response.Error) - } - return response.Channel, response.NoOp, response.AlreadyOpen, nil + + return response.Channel, response.NoOp, response.AlreadyOpen, response.Err() } // JoinConversation joins an existing conversation @@ -523,8 +493,8 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string if err != nil { return nil, "", nil, err } - if !response.Ok { - return nil, "", nil, errors.New(response.Error) + if response.Err() != nil { + return nil, "", nil, response.Err() } var warnings []string if response.ResponseMetaData != nil { @@ -573,7 +543,7 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge values.Add("latest", params.Latest) } if params.Limit != 0 { - values.Add("limit", string(params.Limit)) + values.Add("limit", strconv.Itoa(params.Limit)) } if params.Oldest != "" { values.Add("oldest", params.Oldest) diff --git a/vendor/github.com/nlopes/slack/dnd.go b/vendor/github.com/nlopes/slack/dnd.go index ad8512b..a4cfbe6 100644 --- a/vendor/github.com/nlopes/slack/dnd.go +++ b/vendor/github.com/nlopes/slack/dnd.go @@ -64,10 +64,8 @@ func (api *Client) EndDNDContext(ctx context.Context) error { if err := post(ctx, api.httpclient, "dnd.endDnd", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // EndSnooze ends the current user's snooze mode diff --git a/vendor/github.com/nlopes/slack/files.go b/vendor/github.com/nlopes/slack/files.go index 555d3a5..2381ec3 100644 --- a/vendor/github.com/nlopes/slack/files.go +++ b/vendor/github.com/nlopes/slack/files.go @@ -244,9 +244,9 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam values.Add("content", params.Content) err = postForm(ctx, api.httpclient, SLACK_API+"files.upload", values, response, api.debug) } else if params.File != "" { - err = postLocalWithMultipartResponse(ctx, api.httpclient, SLACK_API+"files.upload", params.File, "file", values, response, api.debug) + err = postLocalWithMultipartResponse(ctx, api.httpclient, "files.upload", params.File, "file", values, response, api.debug) } else if params.Reader != nil { - err = postWithMultipartResponse(ctx, api.httpclient, SLACK_API+"files.upload", params.Filename, "file", values, params.Reader, response, api.debug) + err = postWithMultipartResponse(ctx, api.httpclient, "files.upload", params.Filename, "file", values, params.Reader, response, api.debug) } if err != nil { return nil, err @@ -273,11 +273,8 @@ func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, comment "file": {fileID}, "id": {commentID}, } - if _, err = fileRequest(ctx, api.httpclient, "files.comments.delete", values, api.debug); err != nil { - return err - } - - return nil + _, err = fileRequest(ctx, api.httpclient, "files.comments.delete", values, api.debug) + return err } // DeleteFile deletes a file @@ -292,11 +289,8 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err er "file": {fileID}, } - if _, err = fileRequest(ctx, api.httpclient, "files.delete", values, api.debug); err != nil { - return err - } - - return nil + _, err = fileRequest(ctx, api.httpclient, "files.delete", values, api.debug) + return err } // RevokeFilePublicURL disables public/external sharing for a file diff --git a/vendor/github.com/nlopes/slack/groups.go b/vendor/github.com/nlopes/slack/groups.go index d0e7d91..67e78e9 100644 --- a/vendor/github.com/nlopes/slack/groups.go +++ b/vendor/github.com/nlopes/slack/groups.go @@ -53,9 +53,6 @@ func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error } _, err := groupRequest(ctx, api.httpclient, "groups.archive", values, api.debug) - if err != nil { - return err - } return err } @@ -72,10 +69,7 @@ func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) erro } _, err := groupRequest(ctx, api.httpclient, "groups.unarchive", values, api.debug) - if err != nil { - return err - } - return nil + return err } // CreateGroup creates a private group @@ -215,11 +209,8 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err err "channel": {group}, } - if _, err = groupRequest(ctx, api.httpclient, "groups.leave", values, api.debug); err != nil { - return err - } - - return nil + _, err = groupRequest(ctx, api.httpclient, "groups.leave", values, api.debug) + return err } // KickUserFromGroup kicks a user from a group @@ -235,11 +226,8 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str "user": {user}, } - if _, err = groupRequest(ctx, api.httpclient, "groups.kick", values, api.debug); err != nil { - return err - } - - return nil + _, err = groupRequest(ctx, api.httpclient, "groups.kick", values, api.debug) + return err } // GetGroups retrieves all groups @@ -300,11 +288,8 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string "ts": {ts}, } - if _, err = groupRequest(ctx, api.httpclient, "groups.mark", values, api.debug); err != nil { - return err - } - - return nil + _, err = groupRequest(ctx, api.httpclient, "groups.mark", values, api.debug) + return err } // OpenGroup opens a private group diff --git a/vendor/github.com/nlopes/slack/im.go b/vendor/github.com/nlopes/slack/im.go index 55b24b7..ef47014 100644 --- a/vendor/github.com/nlopes/slack/im.go +++ b/vendor/github.com/nlopes/slack/im.go @@ -87,18 +87,15 @@ func (api *Client) MarkIMChannel(channel, ts string) (err error) { } // MarkIMChannelContext sets the read mark of a direct message channel to a specific point with a custom context -func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) (err error) { +func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) error { values := url.Values{ "token": {api.token}, "channel": {channel}, "ts": {ts}, } - _, err = imRequest(ctx, api.httpclient, "im.mark", values, api.debug) - if err != nil { - return err - } - return + _, err := imRequest(ctx, api.httpclient, "im.mark", values, api.debug) + return err } // GetIMHistory retrieves the direct message channel history diff --git a/vendor/github.com/nlopes/slack/info.go b/vendor/github.com/nlopes/slack/info.go index 49db532..db8534c 100644 --- a/vendor/github.com/nlopes/slack/info.go +++ b/vendor/github.com/nlopes/slack/info.go @@ -1,7 +1,9 @@ package slack import ( + "bytes" "fmt" + "strconv" "time" ) @@ -127,6 +129,19 @@ func (t JSONTime) Time() time.Time { return time.Unix(int64(t), 0) } +// UnmarshalJSON will unmarshal both string and int JSON values +func (t *JSONTime) UnmarshalJSON(buf []byte) error { + s := bytes.Trim(buf, `"`) + + v, err := strconv.Atoi(string(s)) + if err != nil { + return err + } + + *t = JSONTime(int64(v)) + return nil +} + // Team contains details about a team type Team struct { ID string `json:"id"` @@ -156,7 +171,7 @@ type Info struct { type infoResponseFull struct { Info - WebResponse + SlackResponse } // GetBotByID returns a bot given a bot id diff --git a/vendor/github.com/nlopes/slack/messages.go b/vendor/github.com/nlopes/slack/messages.go index 4d9df61..27a5ab3 100644 --- a/vendor/github.com/nlopes/slack/messages.go +++ b/vendor/github.com/nlopes/slack/messages.go @@ -26,7 +26,7 @@ type Msg struct { Timestamp string `json:"ts,omitempty"` ThreadTimestamp string `json:"thread_ts,omitempty"` IsStarred bool `json:"is_starred,omitempty"` - PinnedTo []string `json:"pinned_to, omitempty"` + PinnedTo []string `json:"pinned_to,omitempty"` Attachments []Attachment `json:"attachments,omitempty"` Edited *Edited `json:"edited,omitempty"` LastRead string `json:"last_read,omitempty"` @@ -89,6 +89,7 @@ type Msg struct { // slash commands and interactive messages ResponseType string `json:"response_type,omitempty"` ReplaceOriginal bool `json:"replace_original,omitempty"` + DeleteOriginal bool `json:"delete_original,omitempty"` } // Icon is used for bot messages diff --git a/vendor/github.com/nlopes/slack/misc.go b/vendor/github.com/nlopes/slack/misc.go index 32f2367..17c0dbb 100644 --- a/vendor/github.com/nlopes/slack/misc.go +++ b/vendor/github.com/nlopes/slack/misc.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -18,15 +19,41 @@ import ( "time" ) -type WebResponse struct { - Ok bool `json:"ok"` - Error *WebError `json:"error"` +type SlackResponse struct { + Ok bool `json:"ok"` + Error string `json:"error"` } -type WebError string +func (t SlackResponse) Err() error { + if t.Ok { + return nil + } + + // handle pure text based responses like chat.post + // which while they have a slack response in their data structure + // it doesn't actually get set during parsing. + if strings.TrimSpace(t.Error) == "" { + return nil + } + + return errors.New(t.Error) +} + +// StatusCodeError represents an http response error. +// type httpStatusCode interface { HTTPStatusCode() int } to handle it. +type statusCodeError struct { + Code int + Status string +} + +func (t statusCodeError) Error() string { + // TODO: this is a bad error string, should clean it up with a breaking changes + // merger. + return fmt.Sprintf("Slack server error: %s.", t.Status) +} -func (s WebError) Error() string { - return string(s) +func (t statusCodeError) HTTPStatusCode() int { + return t.Code } type RateLimitedError struct { @@ -63,7 +90,7 @@ func fileUploadReq(ctx context.Context, path, fieldname, filename string, values return req, nil } -func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error { +func parseResponseBody(body io.ReadCloser, intf interface{}, debug bool) error { response, err := ioutil.ReadAll(body) if err != nil { return err @@ -74,7 +101,7 @@ func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error logger.Printf("parseResponseBody: %s\n", string(response)) } - return json.Unmarshal(response, &intf) + return json.Unmarshal(response, intf) } func postLocalWithMultipartResponse(ctx context.Context, client HTTPRequester, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error { @@ -113,10 +140,10 @@ func postWithMultipartResponse(ctx context.Context, client HTTPRequester, path, // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. if resp.StatusCode != http.StatusOK { logResponse(resp, debug) - return fmt.Errorf("Slack server error: %s.", resp.Status) + return statusCodeError{Code: resp.StatusCode, Status: resp.Status} } - return parseResponseBody(resp.Body, &intf, debug) + return parseResponseBody(resp.Body, intf, debug) } func postForm(ctx context.Context, client HTTPRequester, endpoint string, values url.Values, intf interface{}, debug bool) error { @@ -145,10 +172,10 @@ func postForm(ctx context.Context, client HTTPRequester, endpoint string, values // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. if resp.StatusCode != http.StatusOK { logResponse(resp, debug) - return fmt.Errorf("Slack server error: %s.", resp.Status) + return statusCodeError{Code: resp.StatusCode, Status: resp.Status} } - return parseResponseBody(resp.Body, &intf, debug) + return parseResponseBody(resp.Body, intf, debug) } func post(ctx context.Context, client HTTPRequester, path string, values url.Values, intf interface{}, debug bool) error { @@ -180,3 +207,9 @@ func okJsonHandler(rw http.ResponseWriter, r *http.Request) { }) rw.Write(response) } + +type errorString string + +func (t errorString) Error() string { + return string(t) +} diff --git a/vendor/github.com/nlopes/slack/pins.go b/vendor/github.com/nlopes/slack/pins.go index 6b39778..da7fe26 100644 --- a/vendor/github.com/nlopes/slack/pins.go +++ b/vendor/github.com/nlopes/slack/pins.go @@ -24,23 +24,21 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR "token": {api.token}, } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "pins.add", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // RemovePin un-pins an item from a channel @@ -55,23 +53,21 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It "token": {api.token}, } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "pins.remove", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // ListPins returns information about the items a user reacted to. diff --git a/vendor/github.com/nlopes/slack/reactions.go b/vendor/github.com/nlopes/slack/reactions.go index c0556d8..f3746a3 100644 --- a/vendor/github.com/nlopes/slack/reactions.go +++ b/vendor/github.com/nlopes/slack/reactions.go @@ -142,26 +142,24 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite values.Set("name", name) } if item.Channel != "" { - values.Set("channel", string(item.Channel)) + values.Set("channel", item.Channel) } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "reactions.add", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // RemoveReaction removes a reaction emoji from a message, file or file comment. @@ -178,26 +176,24 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item values.Set("name", name) } if item.Channel != "" { - values.Set("channel", string(item.Channel)) + values.Set("channel", item.Channel) } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "reactions.remove", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // GetReactions returns details about the reactions on an item. @@ -211,16 +207,16 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params "token": {api.token}, } if item.Channel != "" { - values.Set("channel", string(item.Channel)) + values.Set("channel", item.Channel) } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } if params.Full != DEFAULT_REACTIONS_FULL { values.Set("full", strconv.FormatBool(params.Full)) diff --git a/vendor/github.com/nlopes/slack/rtm.go b/vendor/github.com/nlopes/slack/rtm.go index 7b55c2a..8dbdb6e 100644 --- a/vendor/github.com/nlopes/slack/rtm.go +++ b/vendor/github.com/nlopes/slack/rtm.go @@ -3,9 +3,11 @@ package slack import ( "context" "encoding/json" - "fmt" "net/url" + "sync" "time" + + "github.com/gorilla/websocket" ) const ( @@ -29,13 +31,11 @@ func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketUR response := &infoResponseFull{} err = post(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api.debug) if err != nil { - return nil, "", fmt.Errorf("post: %s", err) - } - if !response.Ok { - return nil, "", response.Error + return nil, "", err } + api.Debugln("Using URL:", response.Info.URL) - return &response.Info, response.Info.URL, nil + return &response.Info, response.Info.URL, response.Err() } // ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block. @@ -48,7 +48,8 @@ func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) { return api.ConnectRTMContext(ctx) } -// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block with a custom context. +// ConnectRTMContext calls the "rtm.connect" endpoint and returns the +// provided URL and the compact Info block with a custom context. // // To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it. func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) { @@ -56,25 +57,35 @@ func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocket err = post(ctx, api.httpclient, "rtm.connect", url.Values{"token": {api.token}}, response, api.debug) if err != nil { api.Debugf("Failed to connect to RTM: %s", err) - return nil, "", fmt.Errorf("post: %s", err) - } - if !response.Ok { - return nil, "", response.Error + return nil, "", err } + api.Debugln("Using URL:", response.Info.URL) - return &response.Info, response.Info.URL, nil + return &response.Info, response.Info.URL, response.Err() } -// NewRTM returns a RTM, which provides a fully managed connection to -// Slack's websocket-based Real-Time Messaging protocol. -func (api *Client) NewRTM() *RTM { - return api.NewRTMWithOptions(nil) +// RTMOption options for the managed RTM. +type RTMOption func(*RTM) + +// RTMOptionUseStart as of 11th July 2017 you should prefer setting this to false, see: +// https://api.slack.com/changelog/2017-04-start-using-rtm-connect-and-stop-using-rtm-start +func RTMOptionUseStart(b bool) RTMOption { + return func(rtm *RTM) { + rtm.useRTMStart = b + } +} + +// RTMOptionDialer takes a gorilla websocket Dialer and uses it as the +// Dialer when opening the websocket for the RTM connection. +func RTMOptionDialer(d *websocket.Dialer) RTMOption { + return func(rtm *RTM) { + rtm.dialer = d + } } -// NewRTMWithOptions returns a RTM, which provides a fully managed connection to +// NewRTM returns a RTM, which provides a fully managed connection to // Slack's websocket-based Real-Time Messaging protocol. -// This also allows to configure various options available for RTM API. -func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM { +func (api *Client) NewRTM(options ...RTMOption) *RTM { result := &RTM{ Client: *api, IncomingEvents: make(chan RTMEvent, 50), @@ -87,13 +98,23 @@ func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM { forcePing: make(chan bool), rawEvents: make(chan json.RawMessage), idGen: NewSafeID(1), + mu: &sync.Mutex{}, } - if options != nil { - result.useRTMStart = options.UseRTMStart - } else { - result.useRTMStart = true + for _, opt := range options { + opt(result) } return result } + +// NewRTMWithOptions Deprecated just use NewRTM(RTMOptionsUseStart(true)) +// returns a RTM, which provides a fully managed connection to +// Slack's websocket-based Real-Time Messaging protocol. +// This also allows to configure various options available for RTM API. +func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM { + if options != nil { + return api.NewRTM(RTMOptionUseStart(options.UseRTMStart)) + } + return api.NewRTM() +} diff --git a/vendor/github.com/nlopes/slack/search.go b/vendor/github.com/nlopes/slack/search.go index 390dcdb..0cbce29 100644 --- a/vendor/github.com/nlopes/slack/search.go +++ b/vendor/github.com/nlopes/slack/search.go @@ -11,7 +11,7 @@ const ( DEFAULT_SEARCH_SORT = "score" DEFAULT_SEARCH_SORT_DIR = "desc" DEFAULT_SEARCH_HIGHLIGHT = false - DEFAULT_SEARCH_COUNT = 100 + DEFAULT_SEARCH_COUNT = 20 DEFAULT_SEARCH_PAGE = 1 ) @@ -37,17 +37,18 @@ type CtxMessage struct { } type SearchMessage struct { - Type string `json:"type"` - Channel CtxChannel `json:"channel"` - User string `json:"user"` - Username string `json:"username"` - Timestamp string `json:"ts"` - Text string `json:"text"` - Permalink string `json:"permalink"` - Previous CtxMessage `json:"previous"` - Previous2 CtxMessage `json:"previous_2"` - Next CtxMessage `json:"next"` - Next2 CtxMessage `json:"next_2"` + Type string `json:"type"` + Channel CtxChannel `json:"channel"` + User string `json:"user"` + Username string `json:"username"` + Timestamp string `json:"ts"` + Text string `json:"text"` + Permalink string `json:"permalink"` + Attachments []Attachment `json:"attachments"` + Previous CtxMessage `json:"previous"` + Previous2 CtxMessage `json:"previous_2"` + Next CtxMessage `json:"next"` + Next2 CtxMessage `json:"next_2"` } type SearchMessages struct { diff --git a/vendor/github.com/nlopes/slack/slack.go b/vendor/github.com/nlopes/slack/slack.go index ddf42e9..ebc9c2d 100644 --- a/vendor/github.com/nlopes/slack/slack.go +++ b/vendor/github.com/nlopes/slack/slack.go @@ -35,9 +35,17 @@ func SetHTTPClient(client HTTPRequester) { customHTTPClient = client } -type SlackResponse struct { - Ok bool `json:"ok"` - Error string `json:"error"` +// ResponseMetadata holds pagination metadata +type ResponseMetadata struct { + Cursor string `json:"next_cursor"` +} + +func (t *ResponseMetadata) initialize() *ResponseMetadata { + if t != nil { + return t + } + + return &ResponseMetadata{} } type AuthTestResponse struct { diff --git a/vendor/github.com/nlopes/slack/slash.go b/vendor/github.com/nlopes/slack/slash.go index c21a478..f62065a 100644 --- a/vendor/github.com/nlopes/slack/slash.go +++ b/vendor/github.com/nlopes/slack/slash.go @@ -6,17 +6,19 @@ import ( // SlashCommand contains information about a request of the slash command type SlashCommand struct { - Token string `json:"token"` - TeamID string `json:"team_id"` - TeamDomain string `json:"team_domain"` - ChannelID string `json:"channel_id"` - ChannelName string `json:"channel_name"` - UserID string `json:"user_id"` - UserName string `json:"user_name"` - Command string `json:"command"` - Text string `json:"text"` - ResponseURL string `json:"response_url"` - TriggerID string `json:"trigger_id"` + Token string `json:"token"` + TeamID string `json:"team_id"` + TeamDomain string `json:"team_domain"` + EnterpriseID string `json:"enterprise_id,omitempty"` + EnterpriseName string `json:"enterprise_name,omitempty"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + Command string `json:"command"` + Text string `json:"text"` + ResponseURL string `json:"response_url"` + TriggerID string `json:"trigger_id"` } // SlashCommandParse will parse the request of the slash command @@ -27,6 +29,8 @@ func SlashCommandParse(r *http.Request) (s SlashCommand, err error) { s.Token = r.PostForm.Get("token") s.TeamID = r.PostForm.Get("team_id") s.TeamDomain = r.PostForm.Get("team_domain") + s.EnterpriseID = r.PostForm.Get("enterprise_id") + s.EnterpriseName = r.PostForm.Get("enterprise_name") s.ChannelID = r.PostForm.Get("channel_id") s.ChannelName = r.PostForm.Get("channel_name") s.UserID = r.PostForm.Get("user_id") diff --git a/vendor/github.com/nlopes/slack/stars.go b/vendor/github.com/nlopes/slack/stars.go index 785dec5..1fd6ea1 100644 --- a/vendor/github.com/nlopes/slack/stars.go +++ b/vendor/github.com/nlopes/slack/stars.go @@ -48,23 +48,21 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item "token": {api.token}, } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "stars.add", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // RemoveStar removes a starred item from a channel @@ -79,23 +77,21 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I "token": {api.token}, } if item.Timestamp != "" { - values.Set("timestamp", string(item.Timestamp)) + values.Set("timestamp", item.Timestamp) } if item.File != "" { - values.Set("file", string(item.File)) + values.Set("file", item.File) } if item.Comment != "" { - values.Set("file_comment", string(item.Comment)) + values.Set("file_comment", item.Comment) } response := &SlackResponse{} if err := post(ctx, api.httpclient, "stars.remove", values, response, api.debug); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // ListStars returns information about the stars a user added diff --git a/vendor/github.com/nlopes/slack/users.go b/vendor/github.com/nlopes/slack/users.go index 5b3dddc..131eeba 100644 --- a/vendor/github.com/nlopes/slack/users.go +++ b/vendor/github.com/nlopes/slack/users.go @@ -5,37 +5,97 @@ import ( "encoding/json" "errors" "net/url" + "strconv" ) const ( DEFAULT_USER_PHOTO_CROP_X = -1 DEFAULT_USER_PHOTO_CROP_Y = -1 DEFAULT_USER_PHOTO_CROP_W = -1 + errPaginationComplete = errorString("pagination complete") ) // UserProfile contains all the information details of a given user type UserProfile struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - RealName string `json:"real_name"` - RealNameNormalized string `json:"real_name_normalized"` - DisplayName string `json:"display_name"` - DisplayNameNormalized string `json:"display_name_normalized"` - Email string `json:"email"` - Skype string `json:"skype"` - Phone string `json:"phone"` - Image24 string `json:"image_24"` - Image32 string `json:"image_32"` - Image48 string `json:"image_48"` - Image72 string `json:"image_72"` - Image192 string `json:"image_192"` - ImageOriginal string `json:"image_original"` - Title string `json:"title"` - BotID string `json:"bot_id,omitempty"` - ApiAppID string `json:"api_app_id,omitempty"` - StatusText string `json:"status_text,omitempty"` - StatusEmoji string `json:"status_emoji,omitempty"` - Team string `json:"team"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + RealName string `json:"real_name"` + RealNameNormalized string `json:"real_name_normalized"` + DisplayName string `json:"display_name"` + DisplayNameNormalized string `json:"display_name_normalized"` + Email string `json:"email"` + Skype string `json:"skype"` + Phone string `json:"phone"` + Image24 string `json:"image_24"` + Image32 string `json:"image_32"` + Image48 string `json:"image_48"` + Image72 string `json:"image_72"` + Image192 string `json:"image_192"` + ImageOriginal string `json:"image_original"` + Title string `json:"title"` + BotID string `json:"bot_id,omitempty"` + ApiAppID string `json:"api_app_id,omitempty"` + StatusText string `json:"status_text,omitempty"` + StatusEmoji string `json:"status_emoji,omitempty"` + Team string `json:"team"` + Fields UserProfileCustomFields `json:"fields"` +} + +// UserProfileCustomFields represents user profile's custom fields. +// Slack API's response data type is inconsistent so we use the struct. +// For detail, please see below. +// https://github.com/nlopes/slack/pull/298#discussion_r185159233 +type UserProfileCustomFields struct { + fields map[string]UserProfileCustomField +} + +// UnmarshalJSON is the implementation of the json.Unmarshaler interface. +func (fields *UserProfileCustomFields) UnmarshalJSON(b []byte) error { + // https://github.com/nlopes/slack/pull/298#discussion_r185159233 + if string(b) == "[]" { + return nil + } + return json.Unmarshal(b, &fields.fields) +} + +// MarshalJSON is the implementation of the json.Marshaler interface. +func (fields UserProfileCustomFields) MarshalJSON() ([]byte, error) { + if len(fields.fields) == 0 { + return []byte("[]"), nil + } + return json.Marshal(fields.fields) +} + +// ToMap returns a map of custom fields. +func (fields *UserProfileCustomFields) ToMap() map[string]UserProfileCustomField { + return fields.fields +} + +// Len returns the number of custom fields. +func (fields *UserProfileCustomFields) Len() int { + return len(fields.fields) +} + +// SetMap sets a map of custom fields. +func (fields *UserProfileCustomFields) SetMap(m map[string]UserProfileCustomField) { + fields.fields = m +} + +// FieldsMap returns a map of custom fields. +func (profile *UserProfile) FieldsMap() map[string]UserProfileCustomField { + return profile.Fields.ToMap() +} + +// SetFieldsMap sets a map of custom fields. +func (profile *UserProfile) SetFieldsMap(m map[string]UserProfileCustomField) { + profile.Fields.SetMap(m) +} + +// UserProfileCustomField represents a custom user profile field +type UserProfileCustomField struct { + Value string `json:"value"` + Alt string `json:"alt"` + Label string `json:"label"` } // User contains all the information of a user @@ -108,10 +168,11 @@ type TeamIdentity struct { } type userResponseFull struct { - Members []User `json:"members,omitempty"` // ListUsers - User `json:"user,omitempty"` // GetUserInfo - UserPresence // GetUserPresence + Members []User `json:"members,omitempty"` + User `json:"user,omitempty"` + UserPresence SlackResponse + Metadata ResponseMetadata `json:"response_metadata"` } type UserSetPhotoParams struct { @@ -178,23 +239,109 @@ func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, return &response.User, nil } +// GetUsersOption options for the GetUsers method call. +type GetUsersOption func(*UserPagination) + +// GetUsersOptionLimit limit the number of users returned +func GetUsersOptionLimit(n int) GetUsersOption { + return func(p *UserPagination) { + p.limit = n + } +} + +// GetUsersOptionPresence include user presence +func GetUsersOptionPresence(n bool) GetUsersOption { + return func(p *UserPagination) { + p.presence = n + } +} + +func newUserPagination(c *Client, options ...GetUsersOption) (up UserPagination) { + up = UserPagination{ + c: c, + limit: 200, // per slack api documentation. + } + + for _, opt := range options { + opt(&up) + } + + return up +} + +// UserPagination allows for paginating over the users +type UserPagination struct { + Users []User + limit int + presence bool + previousResp *ResponseMetadata + c *Client +} + +// Done checks if the pagination has completed +func (UserPagination) Done(err error) bool { + return err == errPaginationComplete +} + +// Failure checks if pagination failed. +func (t UserPagination) Failure(err error) error { + if t.Done(err) { + return nil + } + + return err +} + +func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error) { + var ( + resp *userResponseFull + ) + + if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") { + return t, errPaginationComplete + } + + t.previousResp = t.previousResp.initialize() + + values := url.Values{ + "limit": {strconv.Itoa(t.limit)}, + "presence": {strconv.FormatBool(t.presence)}, + "token": {t.c.token}, + "cursor": {t.previousResp.Cursor}, + } + + if resp, err = userRequest(ctx, t.c.httpclient, "users.list", values, t.c.debug); err != nil { + return t, err + } + + t.c.Debugf("GetUsersContext: got %d users; metadata %v", len(resp.Members), resp.Metadata) + t.Users = resp.Members + t.previousResp = &resp.Metadata + + return t, nil +} + +// GetUsersPaginated fetches users in a paginated fashion, see GetUsersContext for usage. +func (api *Client) GetUsersPaginated(options ...GetUsersOption) UserPagination { + return newUserPagination(api, options...) +} + // GetUsers returns the list of users (with their detailed information) func (api *Client) GetUsers() ([]User, error) { return api.GetUsersContext(context.Background()) } // GetUsersContext returns the list of users (with their detailed information) with a custom context -func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) { - values := url.Values{ - "token": {api.token}, - "presence": {"1"}, - } +func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) { + var ( + p UserPagination + ) - response, err := userRequest(ctx, api.httpclient, "users.list", values, api.debug) - if err != nil { - return nil, err + for p = api.GetUsersPaginated(); !p.Done(err); p, err = p.Next(ctx) { + results = append(results, p.Users...) } - return response.Members, nil + + return results, p.Failure(err) } // GetUserByEmail will retrieve the complete user information by email @@ -226,11 +373,8 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) { "token": {api.token}, } - if _, err := userRequest(ctx, api.httpclient, "users.setActive", values, api.debug); err != nil { - return err - } - - return nil + _, err = userRequest(ctx, api.httpclient, "users.setActive", values, api.debug) + return err } // SetUserPresence changes the currently authenticated user presence @@ -246,11 +390,7 @@ func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) } _, err := userRequest(ctx, api.httpclient, "users.setPresence", values, api.debug) - if err != nil { - return err - } - return nil - + return err } // GetUserIdentity will retrieve user info available per identity scopes @@ -287,23 +427,21 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params "token": {api.token}, } if params.CropX != DEFAULT_USER_PHOTO_CROP_X { - values.Add("crop_x", string(params.CropX)) + values.Add("crop_x", strconv.Itoa(params.CropX)) } if params.CropY != DEFAULT_USER_PHOTO_CROP_Y { - values.Add("crop_y", string(params.CropY)) + values.Add("crop_y", strconv.Itoa(params.CropX)) } if params.CropW != DEFAULT_USER_PHOTO_CROP_W { - values.Add("crop_w", string(params.CropW)) + values.Add("crop_w", strconv.Itoa(params.CropW)) } - err := postLocalWithMultipartResponse(ctx, api.httpclient, SLACK_API+"users.setPhoto", image, "image", values, response, api.debug) + err := postLocalWithMultipartResponse(ctx, api.httpclient, "users.setPhoto", image, "image", values, response, api.debug) if err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // DeleteUserPhoto deletes the current authenticated user's profile image @@ -322,10 +460,8 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) error { if err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - return nil + + return response.Err() } // SetUserCustomStatus will set a custom status and emoji for the currently @@ -392,3 +528,31 @@ func (api *Client) UnsetUserCustomStatus() error { func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error { return api.SetUserCustomStatusContext(ctx, "", "") } + +// GetUserProfile retrieves a user's profile information. +func (api *Client) GetUserProfile(userID string, includeLabels bool) (*UserProfile, error) { + return api.GetUserProfileContext(context.Background(), userID, includeLabels) +} + +type getUserProfileResponse struct { + SlackResponse + Profile *UserProfile `json:"profile"` +} + +// GetUserProfileContext retrieves a user's profile information with a context. +func (api *Client) GetUserProfileContext(ctx context.Context, userID string, includeLabels bool) (*UserProfile, error) { + values := url.Values{"token": {api.token}, "user": {userID}} + if includeLabels { + values.Add("include_labels", "true") + } + resp := &getUserProfileResponse{} + + err := post(ctx, api.httpclient, "users.profile.get", values, &resp, api.debug) + if err != nil { + return nil, err + } + if !resp.Ok { + return nil, errors.New(resp.Error) + } + return resp.Profile, nil +} diff --git a/vendor/github.com/nlopes/slack/websocket.go b/vendor/github.com/nlopes/slack/websocket.go index f28c945..641bdf3 100644 --- a/vendor/github.com/nlopes/slack/websocket.go +++ b/vendor/github.com/nlopes/slack/websocket.go @@ -3,6 +3,7 @@ package slack import ( "encoding/json" "errors" + "sync" "time" "github.com/gorilla/websocket" @@ -44,6 +45,13 @@ type RTM struct { // rtm.start to connect to Slack, otherwise it will use // rtm.connect useRTMStart bool + + // dialer is a gorilla/websocket Dialer. If nil, use the default + // Dialer. + dialer *websocket.Dialer + + // mu is mutex used to prevent RTM connection race conditions + mu *sync.Mutex } // RTMOptions allows configuration of various options available for RTM messaging @@ -60,6 +68,9 @@ type RTMOptions struct { // Disconnect and wait, blocking until a successful disconnection. func (rtm *RTM) Disconnect() error { + // avoid RTM disconnect race conditions + rtm.mu.Lock() + defer rtm.mu.Unlock() // this channel is always closed on disconnect. lets the ManagedConnection() function // properly clean up. close(rtm.disconnected) diff --git a/vendor/github.com/nlopes/slack/websocket_managed_conn.go b/vendor/github.com/nlopes/slack/websocket_managed_conn.go index 7f7f353..a78b341 100644 --- a/vendor/github.com/nlopes/slack/websocket_managed_conn.go +++ b/vendor/github.com/nlopes/slack/websocket_managed_conn.go @@ -25,18 +25,26 @@ import ( // // The defined error events are located in websocket_internals.go. func (rtm *RTM) ManageConnection() { - var connectionCount int + var ( + err error + connectionCount int + info *Info + conn *websocket.Conn + ) + for { + // BEGIN SENSITIVE CODE, make sure lock is unlocked in this section. + rtm.mu.Lock() connectionCount++ // start trying to connect // the returned err is already passed onto the IncomingEvents channel - info, conn, err := rtm.connect(connectionCount, rtm.useRTMStart) - // if err != nil then the connection is sucessful - otherwise it is - // fatal - if err != nil { + if info, conn, err = rtm.connect(connectionCount, rtm.useRTMStart); err != nil { + // when the connection is unsuccessful its fatal, and we need to bail out. rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err) + rtm.mu.Unlock() return } + rtm.info = info rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{ ConnectionCount: connectionCount, @@ -45,6 +53,8 @@ func (rtm *RTM) ManageConnection() { rtm.conn = conn rtm.isConnected = true + rtm.mu.Unlock() + // END SENSITIVE CODE rtm.Debugf("RTM connection succeeded on try %d", connectionCount) @@ -71,6 +81,12 @@ func (rtm *RTM) ManageConnection() { // If useRTMStart is false then it uses rtm.connect to create the connection, // otherwise it uses rtm.start. func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocket.Conn, error) { + const ( + errInvalidAuth = "invalid_auth" + errInactiveAccount = "account_inactive" + errMissingAuthToken = "not_authed" + ) + // used to provide exponential backoff wait time with jitter before trying // to connect to slack again boff := &backoff{ @@ -91,11 +107,14 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke if err == nil { return info, conn, nil } - // check for fatal errors - currently only invalid_auth - if sErr, ok := err.(*WebError); ok && (sErr.Error() == "invalid_auth" || sErr.Error() == "account_inactive") { + + // check for fatal errors + switch err.Error() { + case errInvalidAuth, errInactiveAccount, errMissingAuthToken: rtm.Debugf("Invalid auth when connecting with RTM: %s", err) rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}} - return nil, nil, sErr + return nil, nil, err + default: } // any other errors are treated as recoverable and we try again after @@ -107,7 +126,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // check if Disconnect() has been invoked. select { - case _ = <-rtm.disconnected: + case <-rtm.disconnected: rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: true}} return nil, nil, fmt.Errorf("disconnect received while trying to connect") default: @@ -124,10 +143,10 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // startRTMAndDial attempts to connect to the slack websocket. If useRTMStart is true, // then it returns the full information returned by the "rtm.start" method on the // slack API. Else it uses the "rtm.connect" method to connect -func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error) { - var info *Info - var url string - var err error +func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn, err error) { + var ( + url string + ) if useRTMStart { rtm.Debugf("Starting RTM") @@ -145,7 +164,11 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error // Only use HTTPS for connections to prevent MITM attacks on the connection. upgradeHeader := http.Header{} upgradeHeader.Add("Origin", "https://api.slack.com") - conn, _, err := websocket.DefaultDialer.Dial(url, upgradeHeader) + dialer := websocket.DefaultDialer + if rtm.dialer != nil { + dialer = rtm.dialer + } + conn, _, err := dialer.Dial(url, upgradeHeader) if err != nil { rtm.Debugf("Failed to dial to the websocket: %s", err) return nil, nil, err @@ -220,7 +243,9 @@ func (rtm *RTM) handleIncomingEvents(keepRunning <-chan bool) { case <-keepRunning: return default: - rtm.receiveIncomingEvent() + if err := rtm.receiveIncomingEvent(); err != nil { + return + } } } } @@ -283,29 +308,33 @@ func (rtm *RTM) ping() error { // receiveIncomingEvent attempts to receive an event from the RTM's websocket. // This will block until a frame is available from the websocket. -func (rtm *RTM) receiveIncomingEvent() { +// If the read from the websocket results in a fatal error, this function will return non-nil. +func (rtm *RTM) receiveIncomingEvent() error { event := json.RawMessage{} err := rtm.conn.ReadJSON(&event) - if err == io.EOF { + switch { + case err == io.ErrUnexpectedEOF: // EOF's don't seem to signify a failed connection so instead we ignore // them here and detect a failed connection upon attempting to send a // 'PING' message - // trigger a 'PING' to detect pontential websocket disconnect + // trigger a 'PING' to detect potential websocket disconnect rtm.forcePing <- true - return - } else if err != nil { + case err != nil: + // All other errors from ReadJSON come from NextReader, and should + // kill the read loop and force a reconnect. rtm.IncomingEvents <- RTMEvent{"incoming_error", &IncomingEventError{ ErrorObj: err, }} - // force a ping here too? - return - } else if len(event) == 0 { + rtm.killChannel <- false + return err + case len(event) == 0: rtm.Debugln("Received empty event") - return + default: + rtm.Debugln("Incoming Event:", string(event[:])) + rtm.rawEvents <- event } - rtm.Debugln("Incoming Event:", string(event[:])) - rtm.rawEvents <- event + return nil } // handleRawEvent takes a raw JSON message received from the slack websocket @@ -381,7 +410,7 @@ func (rtm *RTM) handlePong(event json.RawMessage) { // correct struct then this sends an UnmarshallingErrorEvent to the // IncomingEvents channel. func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) { - v, exists := eventMapping[typeStr] + v, exists := EventMapping[typeStr] if !exists { rtm.Debugf("RTM Error, received unmapped event %q: %s\n", typeStr, string(event)) err := fmt.Errorf("RTM Error: Received unmapped event %q: %s\n", typeStr, string(event)) @@ -400,10 +429,10 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) { rtm.IncomingEvents <- RTMEvent{typeStr, recvEvent} } -// eventMapping holds a mapping of event names to their corresponding struct +// EventMapping holds a mapping of event names to their corresponding struct // implementations. The structs should be instances of the unmarshalling // target for the matching event type. -var eventMapping = map[string]interface{}{ +var EventMapping = map[string]interface{}{ "message": MessageEvent{}, "presence_change": PresenceChangeEvent{}, "user_typing": UserTypingEvent{}, @@ -481,4 +510,7 @@ var eventMapping = map[string]interface{}{ "accounts_changed": AccountsChangedEvent{}, "reconnect_url": ReconnectUrlEvent{}, + + "member_joined_channel": MemberJoinedChannelEvent{}, + "member_left_channel": MemberLeftChannelEvent{}, } diff --git a/vendor/github.com/nlopes/slack/websocket_misc.go b/vendor/github.com/nlopes/slack/websocket_misc.go index ad283ea..16f48c7 100644 --- a/vendor/github.com/nlopes/slack/websocket_misc.go +++ b/vendor/github.com/nlopes/slack/websocket_misc.go @@ -80,7 +80,7 @@ type EmojiChangedEvent struct { SubType string `json:"subtype"` Name string `json:"name"` Names []string `json:"names"` - Value string `json:"value"` + Value string `json:"value"` EventTimestamp string `json:"event_ts"` } @@ -119,3 +119,22 @@ type ReconnectUrlEvent struct { Type string `json:"type"` URL string `json:"url"` } + +// MemberJoinedChannelEvent, a user joined a public or private channel +type MemberJoinedChannelEvent struct { + Type string `json:"type"` + User string `json:"user"` + Channel string `json:"channel"` + ChannelType string `json:"channel_type"` + Team string `json:"team"` + Inviter string `json:"inviter"` +} + +// MemberJoinedChannelEvent, a user left a public or private channel +type MemberLeftChannelEvent struct { + Type string `json:"type"` + User string `json:"user"` + Channel string `json:"channel"` + ChannelType string `json:"channel_type"` + Team string `json:"team"` +} diff --git a/vendor/github.com/satori/go.uuid/.travis.yml b/vendor/github.com/satori/go.uuid/.travis.yml new file mode 100644 index 0000000..20dd53b --- /dev/null +++ b/vendor/github.com/satori/go.uuid/.travis.yml @@ -0,0 +1,23 @@ +language: go +sudo: false +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - tip +matrix: + allow_failures: + - go: tip + fast_finish: true +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -service=travis-ci +notifications: + email: false diff --git a/vendor/github.com/satori/go.uuid/LICENSE b/vendor/github.com/satori/go.uuid/LICENSE new file mode 100644 index 0000000..926d549 --- /dev/null +++ b/vendor/github.com/satori/go.uuid/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013-2018 by Maxim Bublis + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/satori/go.uuid/README.md b/vendor/github.com/satori/go.uuid/README.md new file mode 100644 index 0000000..7b1a722 --- /dev/null +++ b/vendor/github.com/satori/go.uuid/README.md @@ -0,0 +1,65 @@ +# UUID package for Go language + +[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid) +[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) +[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid) + +This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. + +With 100% test coverage and benchmarks out of box. + +Supported versions: +* Version 1, based on timestamp and MAC address (RFC 4122) +* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) +* Version 3, based on MD5 hashing (RFC 4122) +* Version 4, based on random numbers (RFC 4122) +* Version 5, based on SHA-1 hashing (RFC 4122) + +## Installation + +Use the `go` command: + + $ go get github.com/satori/go.uuid + +## Requirements + +UUID package requires Go >= 1.2. + +## Example + +```go +package main + +import ( + "fmt" + "github.com/satori/go.uuid" +) + +func main() { + // Creating UUID Version 4 + u1 := uuid.NewV4() + fmt.Printf("UUIDv4: %s\n", u1) + + // Parsing UUID from string input + u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + fmt.Printf("Something gone wrong: %s", err) + } + fmt.Printf("Successfully parsed: %s", u2) +} +``` + +## Documentation + +[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project. + +## Links +* [RFC 4122](http://tools.ietf.org/html/rfc4122) +* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) + +## Copyright + +Copyright (C) 2013-2018 by Maxim Bublis . + +UUID package released under MIT License. +See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details. diff --git a/vendor/github.com/satori/go.uuid/codec.go b/vendor/github.com/satori/go.uuid/codec.go new file mode 100644 index 0000000..656892c --- /dev/null +++ b/vendor/github.com/satori/go.uuid/codec.go @@ -0,0 +1,206 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "bytes" + "encoding/hex" + "fmt" +) + +// FromBytes returns UUID converted from raw byte slice input. +// It will return error if the slice isn't 16 bytes long. +func FromBytes(input []byte) (u UUID, err error) { + err = u.UnmarshalBinary(input) + return +} + +// FromBytesOrNil returns UUID converted from raw byte slice input. +// Same behavior as FromBytes, but returns a Nil UUID on error. +func FromBytesOrNil(input []byte) UUID { + uuid, err := FromBytes(input) + if err != nil { + return Nil + } + return uuid +} + +// FromString returns UUID parsed from string input. +// Input is expected in a form accepted by UnmarshalText. +func FromString(input string) (u UUID, err error) { + err = u.UnmarshalText([]byte(input)) + return +} + +// FromStringOrNil returns UUID parsed from string input. +// Same behavior as FromString, but returns a Nil UUID on error. +func FromStringOrNil(input string) UUID { + uuid, err := FromString(input) + if err != nil { + return Nil + } + return uuid +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The encoding is the same as returned by String. +func (u UUID) MarshalText() (text []byte, err error) { + text = []byte(u.String()) + return +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Following formats are supported: +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" +// "6ba7b8109dad11d180b400c04fd430c8" +// ABNF for supported UUID text representation follows: +// uuid := canonical | hashlike | braced | urn +// plain := canonical | hashlike +// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct +// hashlike := 12hexoct +// braced := '{' plain '}' +// urn := URN ':' UUID-NID ':' plain +// URN := 'urn' +// UUID-NID := 'uuid' +// 12hexoct := 6hexoct 6hexoct +// 6hexoct := 4hexoct 2hexoct +// 4hexoct := 2hexoct 2hexoct +// 2hexoct := hexoct hexoct +// hexoct := hexdig hexdig +// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | +// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | +// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' +func (u *UUID) UnmarshalText(text []byte) (err error) { + switch len(text) { + case 32: + return u.decodeHashLike(text) + case 36: + return u.decodeCanonical(text) + case 38: + return u.decodeBraced(text) + case 41: + fallthrough + case 45: + return u.decodeURN(text) + default: + return fmt.Errorf("uuid: incorrect UUID length: %s", text) + } +} + +// decodeCanonical decodes UUID string in format +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8". +func (u *UUID) decodeCanonical(t []byte) (err error) { + if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { + return fmt.Errorf("uuid: incorrect UUID format %s", t) + } + + src := t[:] + dst := u[:] + + for i, byteGroup := range byteGroups { + if i > 0 { + src = src[1:] // skip dash + } + _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) + if err != nil { + return + } + src = src[byteGroup:] + dst = dst[byteGroup/2:] + } + + return +} + +// decodeHashLike decodes UUID string in format +// "6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodeHashLike(t []byte) (err error) { + src := t[:] + dst := u[:] + + if _, err = hex.Decode(dst, src); err != nil { + return err + } + return +} + +// decodeBraced decodes UUID string in format +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format +// "{6ba7b8109dad11d180b400c04fd430c8}". +func (u *UUID) decodeBraced(t []byte) (err error) { + l := len(t) + + if t[0] != '{' || t[l-1] != '}' { + return fmt.Errorf("uuid: incorrect UUID format %s", t) + } + + return u.decodePlain(t[1 : l-1]) +} + +// decodeURN decodes UUID string in format +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format +// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodeURN(t []byte) (err error) { + total := len(t) + + urn_uuid_prefix := t[:9] + + if !bytes.Equal(urn_uuid_prefix, urnPrefix) { + return fmt.Errorf("uuid: incorrect UUID format: %s", t) + } + + return u.decodePlain(t[9:total]) +} + +// decodePlain decodes UUID string in canonical format +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format +// "6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodePlain(t []byte) (err error) { + switch len(t) { + case 32: + return u.decodeHashLike(t) + case 36: + return u.decodeCanonical(t) + default: + return fmt.Errorf("uuid: incorrrect UUID length: %s", t) + } +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (u UUID) MarshalBinary() (data []byte, err error) { + data = u.Bytes() + return +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// It will return error if the slice isn't 16 bytes long. +func (u *UUID) UnmarshalBinary(data []byte) (err error) { + if len(data) != Size { + err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) + return + } + copy(u[:], data) + + return +} diff --git a/vendor/github.com/satori/go.uuid/generator.go b/vendor/github.com/satori/go.uuid/generator.go new file mode 100644 index 0000000..3f2f1da --- /dev/null +++ b/vendor/github.com/satori/go.uuid/generator.go @@ -0,0 +1,239 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "encoding/binary" + "hash" + "net" + "os" + "sync" + "time" +) + +// Difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). +const epochStart = 122192928000000000 + +var ( + global = newDefaultGenerator() + + epochFunc = unixTimeFunc + posixUID = uint32(os.Getuid()) + posixGID = uint32(os.Getgid()) +) + +// NewV1 returns UUID based on current timestamp and MAC address. +func NewV1() UUID { + return global.NewV1() +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func NewV2(domain byte) UUID { + return global.NewV2(domain) +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func NewV3(ns UUID, name string) UUID { + return global.NewV3(ns, name) +} + +// NewV4 returns random generated UUID. +func NewV4() UUID { + return global.NewV4() +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func NewV5(ns UUID, name string) UUID { + return global.NewV5(ns, name) +} + +// Generator provides interface for generating UUIDs. +type Generator interface { + NewV1() UUID + NewV2(domain byte) UUID + NewV3(ns UUID, name string) UUID + NewV4() UUID + NewV5(ns UUID, name string) UUID +} + +// Default generator implementation. +type generator struct { + storageOnce sync.Once + storageMutex sync.Mutex + + lastTime uint64 + clockSequence uint16 + hardwareAddr [6]byte +} + +func newDefaultGenerator() Generator { + return &generator{} +} + +// NewV1 returns UUID based on current timestamp and MAC address. +func (g *generator) NewV1() UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := g.getStorage() + + binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + + copy(u[10:], hardwareAddr) + + u.SetVersion(V1) + u.SetVariant(VariantRFC4122) + + return u +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func (g *generator) NewV2(domain byte) UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := g.getStorage() + + switch domain { + case DomainPerson: + binary.BigEndian.PutUint32(u[0:], posixUID) + case DomainGroup: + binary.BigEndian.PutUint32(u[0:], posixGID) + } + + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + u[9] = domain + + copy(u[10:], hardwareAddr) + + u.SetVersion(V2) + u.SetVariant(VariantRFC4122) + + return u +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func (g *generator) NewV3(ns UUID, name string) UUID { + u := newFromHash(md5.New(), ns, name) + u.SetVersion(V3) + u.SetVariant(VariantRFC4122) + + return u +} + +// NewV4 returns random generated UUID. +func (g *generator) NewV4() UUID { + u := UUID{} + g.safeRandom(u[:]) + u.SetVersion(V4) + u.SetVariant(VariantRFC4122) + + return u +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func (g *generator) NewV5(ns UUID, name string) UUID { + u := newFromHash(sha1.New(), ns, name) + u.SetVersion(V5) + u.SetVariant(VariantRFC4122) + + return u +} + +func (g *generator) initStorage() { + g.initClockSequence() + g.initHardwareAddr() +} + +func (g *generator) initClockSequence() { + buf := make([]byte, 2) + g.safeRandom(buf) + g.clockSequence = binary.BigEndian.Uint16(buf) +} + +func (g *generator) initHardwareAddr() { + interfaces, err := net.Interfaces() + if err == nil { + for _, iface := range interfaces { + if len(iface.HardwareAddr) >= 6 { + copy(g.hardwareAddr[:], iface.HardwareAddr) + return + } + } + } + + // Initialize hardwareAddr randomly in case + // of real network interfaces absence + g.safeRandom(g.hardwareAddr[:]) + + // Set multicast bit as recommended in RFC 4122 + g.hardwareAddr[0] |= 0x01 +} + +func (g *generator) safeRandom(dest []byte) { + if _, err := rand.Read(dest); err != nil { + panic(err) + } +} + +// Returns UUID v1/v2 storage state. +// Returns epoch timestamp, clock sequence, and hardware address. +func (g *generator) getStorage() (uint64, uint16, []byte) { + g.storageOnce.Do(g.initStorage) + + g.storageMutex.Lock() + defer g.storageMutex.Unlock() + + timeNow := epochFunc() + // Clock changed backwards since last UUID generation. + // Should increase clock sequence. + if timeNow <= g.lastTime { + g.clockSequence++ + } + g.lastTime = timeNow + + return timeNow, g.clockSequence, g.hardwareAddr[:] +} + +// Returns difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and current time. +// This is default epoch calculation function. +func unixTimeFunc() uint64 { + return epochStart + uint64(time.Now().UnixNano()/100) +} + +// Returns UUID based on hashing of namespace UUID and name. +func newFromHash(h hash.Hash, ns UUID, name string) UUID { + u := UUID{} + h.Write(ns[:]) + h.Write([]byte(name)) + copy(u[:], h.Sum(nil)) + + return u +} diff --git a/vendor/github.com/satori/go.uuid/sql.go b/vendor/github.com/satori/go.uuid/sql.go new file mode 100644 index 0000000..56759d3 --- /dev/null +++ b/vendor/github.com/satori/go.uuid/sql.go @@ -0,0 +1,78 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Value implements the driver.Valuer interface. +func (u UUID) Value() (driver.Value, error) { + return u.String(), nil +} + +// Scan implements the sql.Scanner interface. +// A 16-byte slice is handled by UnmarshalBinary, while +// a longer byte slice or a string is handled by UnmarshalText. +func (u *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + if len(src) == Size { + return u.UnmarshalBinary(src) + } + return u.UnmarshalText(src) + + case string: + return u.UnmarshalText([]byte(src)) + } + + return fmt.Errorf("uuid: cannot convert %T to UUID", src) +} + +// NullUUID can be used with the standard sql package to represent a +// UUID value that can be NULL in the database +type NullUUID struct { + UUID UUID + Valid bool +} + +// Value implements the driver.Valuer interface. +func (u NullUUID) Value() (driver.Value, error) { + if !u.Valid { + return nil, nil + } + // Delegate to UUID Value function + return u.UUID.Value() +} + +// Scan implements the sql.Scanner interface. +func (u *NullUUID) Scan(src interface{}) error { + if src == nil { + u.UUID, u.Valid = Nil, false + return nil + } + + // Delegate to UUID Scan function + u.Valid = true + return u.UUID.Scan(src) +} diff --git a/vendor/github.com/satori/go.uuid/uuid.go b/vendor/github.com/satori/go.uuid/uuid.go new file mode 100644 index 0000000..a2b8e2c --- /dev/null +++ b/vendor/github.com/satori/go.uuid/uuid.go @@ -0,0 +1,161 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Package uuid provides implementation of Universally Unique Identifier (UUID). +// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and +// version 2 (as specified in DCE 1.1). +package uuid + +import ( + "bytes" + "encoding/hex" +) + +// Size of a UUID in bytes. +const Size = 16 + +// UUID representation compliant with specification +// described in RFC 4122. +type UUID [Size]byte + +// UUID versions +const ( + _ byte = iota + V1 + V2 + V3 + V4 + V5 +) + +// UUID layout variants. +const ( + VariantNCS byte = iota + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// UUID DCE domains. +const ( + DomainPerson = iota + DomainGroup + DomainOrg +) + +// String parse helpers. +var ( + urnPrefix = []byte("urn:uuid:") + byteGroups = []int{8, 4, 4, 4, 12} +) + +// Nil is special form of UUID that is specified to have all +// 128 bits set to zero. +var Nil = UUID{} + +// Predefined namespace UUIDs. +var ( + NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) +) + +// Equal returns true if u1 and u2 equals, otherwise returns false. +func Equal(u1 UUID, u2 UUID) bool { + return bytes.Equal(u1[:], u2[:]) +} + +// Version returns algorithm version used to generate UUID. +func (u UUID) Version() byte { + return u[6] >> 4 +} + +// Variant returns UUID layout variant. +func (u UUID) Variant() byte { + switch { + case (u[8] >> 7) == 0x00: + return VariantNCS + case (u[8] >> 6) == 0x02: + return VariantRFC4122 + case (u[8] >> 5) == 0x06: + return VariantMicrosoft + case (u[8] >> 5) == 0x07: + fallthrough + default: + return VariantFuture + } +} + +// Bytes returns bytes slice representation of UUID. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Returns canonical string representation of UUID: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := make([]byte, 36) + + hex.Encode(buf[0:8], u[0:4]) + buf[8] = '-' + hex.Encode(buf[9:13], u[4:6]) + buf[13] = '-' + hex.Encode(buf[14:18], u[6:8]) + buf[18] = '-' + hex.Encode(buf[19:23], u[8:10]) + buf[23] = '-' + hex.Encode(buf[24:], u[10:]) + + return string(buf) +} + +// SetVersion sets version bits. +func (u *UUID) SetVersion(v byte) { + u[6] = (u[6] & 0x0f) | (v << 4) +} + +// SetVariant sets variant bits. +func (u *UUID) SetVariant(v byte) { + switch v { + case VariantNCS: + u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) + case VariantRFC4122: + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) + case VariantMicrosoft: + u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) + case VariantFuture: + fallthrough + default: + u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) + } +} + +// Must is a helper that wraps a call to a function returning (UUID, error) +// and panics if the error is non-nil. It is intended for use in variable +// initializations such as +// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); +func Must(u UUID, err error) UUID { + if err != nil { + panic(err) + } + return u +} diff --git a/vendor/golang.org/x/sys/unix/syscall_bsd.go b/vendor/golang.org/x/sys/unix/syscall_bsd.go index 53fb851..33c8b5f 100644 --- a/vendor/golang.org/x/sys/unix/syscall_bsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_bsd.go @@ -206,7 +206,7 @@ func (sa *SockaddrDatalink) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrDatalink, nil } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_LINK: pp := (*RawSockaddrDatalink)(unsafe.Pointer(rsa)) @@ -286,7 +286,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { Close(nfd) return 0, nil, ECONNABORTED } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -306,7 +306,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { rsa.Addr.Family = AF_UNIX rsa.Addr.Len = SizeofSockaddrUnix } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } //sysnb socketpair(domain int, typ int, proto int, fd *[2]int32) (err error) @@ -356,7 +356,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from recvflags = int(msg.Flags) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index b5072de..e34abe2 100644 --- a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -87,7 +87,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index ba9df4a..5561a3e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -89,7 +89,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 9908030..690c2c8 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -489,6 +489,47 @@ func (sa *SockaddrL2) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrL2, nil } +// SockaddrRFCOMM implements the Sockaddr interface for AF_BLUETOOTH type sockets +// using the RFCOMM protocol. +// +// Server example: +// +// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM) +// _ = unix.Bind(fd, &unix.SockaddrRFCOMM{ +// Channel: 1, +// Addr: [6]uint8{0, 0, 0, 0, 0, 0}, // BDADDR_ANY or 00:00:00:00:00:00 +// }) +// _ = Listen(fd, 1) +// nfd, sa, _ := Accept(fd) +// fmt.Printf("conn addr=%v fd=%d", sa.(*unix.SockaddrRFCOMM).Addr, nfd) +// Read(nfd, buf) +// +// Client example: +// +// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM) +// _ = Connect(fd, &SockaddrRFCOMM{ +// Channel: 1, +// Addr: [6]byte{0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc}, // CC:BB:AA:33:22:11 +// }) +// Write(fd, []byte(`hello`)) +type SockaddrRFCOMM struct { + // Addr represents a bluetooth address, byte ordering is little-endian. + Addr [6]uint8 + + // Channel is a designated bluetooth channel, only 1-30 are available for use. + // Since Linux 2.6.7 and further zero value is the first available channel. + Channel uint8 + + raw RawSockaddrRFCOMM +} + +func (sa *SockaddrRFCOMM) sockaddr() (unsafe.Pointer, _Socklen, error) { + sa.raw.Family = AF_BLUETOOTH + sa.raw.Channel = sa.Channel + sa.raw.Bdaddr = sa.Addr + return unsafe.Pointer(&sa.raw), SizeofSockaddrRFCOMM, nil +} + // SockaddrCAN implements the Sockaddr interface for AF_CAN type sockets. // The RxID and TxID fields are used for transport protocol addressing in // (CAN_TP16, CAN_TP20, CAN_MCNET, and CAN_ISOTP), they can be left with @@ -651,7 +692,7 @@ func (sa *SockaddrVM) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrVM, nil } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_NETLINK: pp := (*RawSockaddrNetlink)(unsafe.Pointer(rsa)) @@ -728,6 +769,30 @@ func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { Port: pp.Port, } return sa, nil + case AF_BLUETOOTH: + proto, err := GetsockoptInt(fd, SOL_SOCKET, SO_PROTOCOL) + if err != nil { + return nil, err + } + // only BTPROTO_L2CAP and BTPROTO_RFCOMM can accept connections + switch proto { + case BTPROTO_L2CAP: + pp := (*RawSockaddrL2)(unsafe.Pointer(rsa)) + sa := &SockaddrL2{ + PSM: pp.Psm, + CID: pp.Cid, + Addr: pp.Bdaddr, + AddrType: pp.Bdaddr_type, + } + return sa, nil + case BTPROTO_RFCOMM: + pp := (*RawSockaddrRFCOMM)(unsafe.Pointer(rsa)) + sa := &SockaddrRFCOMM{ + Channel: pp.Channel, + Addr: pp.Bdaddr, + } + return sa, nil + } } return nil, EAFNOSUPPORT } @@ -739,7 +804,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { if err != nil { return } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -757,7 +822,7 @@ func Accept4(fd int, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -771,7 +836,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { if err = getsockname(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } func GetsockoptIPMreqn(fd, level, opt int) (*IPMreqn, error) { @@ -960,7 +1025,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from recvflags = int(msg.Flags) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index 820ef77..a05337d 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -112,7 +112,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { if err = getsockname(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } // GetsockoptString returns the string value of the socket option opt for the @@ -360,7 +360,7 @@ func Futimes(fd int, tv []Timeval) error { return futimesat(fd, nil, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_UNIX: pp := (*RawSockaddrUnix)(unsafe.Pointer(rsa)) @@ -411,7 +411,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { if nfd == -1 { return } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -448,7 +448,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from oobn = int(msg.Accrightslen) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_unix.go b/vendor/golang.org/x/sys/unix/syscall_unix.go index b835bad..95b2180 100644 --- a/vendor/golang.org/x/sys/unix/syscall_unix.go +++ b/vendor/golang.org/x/sys/unix/syscall_unix.go @@ -219,7 +219,7 @@ func Getpeername(fd int) (sa Sockaddr, err error) { if err = getpeername(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } func GetsockoptByte(fd, level, opt int) (value byte, err error) { @@ -291,7 +291,7 @@ func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) { return } if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index e89bc6b..4c25003 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -248,6 +248,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -401,6 +408,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index d95372b..2e4d709 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -250,6 +250,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -405,6 +412,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index 77875ba..bf38e5e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -404,6 +411,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index 5a9df69..972c1b8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index dcb239d..783e70e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -249,6 +249,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -402,6 +409,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 9cf85f7..5c6ea71 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 6fd66e7..93effc8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index faa5b3e..cc5ca24 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -249,6 +249,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -402,6 +409,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index ad4c452..712f640 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -252,6 +252,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -407,6 +414,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 1fdb2f2..1be4532 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -252,6 +252,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -407,6 +414,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index d32079d..932b655 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -250,6 +250,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -405,6 +412,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10