Beispiel #1
0
func (c *Client) registerOnBotmetrics() {
	shouldRegister := true

	key := fmt.Sprintf("botmetrics:%s", c.TeamId)
	redisClient := redisclient.Client()
	boolCmd := redisClient.HSetNX(os.Getenv("RELAX_MUTEX_KEY"), key, "ok")
	if boolCmd != nil {
		shouldRegister = boolCmd.Val()
	}

	if shouldRegister {
		bm, err := botmetrics.NewBotmetricsClient()
		if err == nil {
			status := bm.RegisterBot(c.Token, -1)
			if status {
				log.WithFields(log.Fields{
					"teamId": c.TeamId,
				}).Info("registered on botmetrics")
			} else {
				log.WithFields(log.Fields{
					"teamId": c.TeamId,
				}).Error("error registering on botmetrics")
			}
		}
	}
}
Beispiel #2
0
// InitClients initializes all clients which are present in Redis on boot.
// This looks for all hashes in the key RELAX_BOTS_KEY and calls NewClient for each
// hash present in the redis key. It also starts a loop to listen to REDIS_BOTS_PUBSUB
// when new clients need to be started. It listens to Redis on pubsub instead of a queue
// because there can be multiple instances of Relax running and they all need to start
// a Slack client.
func InitClients() {
	redisClient := redisclient.Client()

	resultCmd := redisClient.HGetAll(os.Getenv("RELAX_BOTS_KEY"))
	result := resultCmd.Val()

	for i := 0; i < len(result); i += 2 {
		key := result[i]
		val := result[i+1]

		c, err := NewClient(val)

		if err != nil {
			log.WithFields(log.Fields{
				"team":  val,
				"error": err,
			}).Error("starting slack client")
		} else {
			go c.LoginAndStart()
			Clients[key] = c
		}
	}

	go startReadFromRedisPubSubLoop()
}
Beispiel #3
0
// Start starts a websocket connection to Slack's servers and starts listening for messages
// If it detects an "invalid_auth" or "inactive_account" message, it means that the token provided by the user
// has expired or is incorrect and so it sends a "disable_bot" event back to the user
// so that they can take remedial action.
func (c *Client) Start() error {
	// Make connection to redis now
	c.redisClient = redisclient.Client()

	if c.data.Ok == true {
		conn, _, err := websocket.DefaultDialer.Dial(c.data.Url, http.Header{})
		if err != nil {
			log.WithFields(log.Fields{
				"team":  c.TeamId,
				"error": err,
			}).Error("dialing connection to Slack websocket")

			return err
		} else {
			c.conn = conn
		}

		go c.startReadFromSlackLoop()
	} else {
		// Bot has been disabled by the user,
		// so we need to mark it as disabled
		if c.data.Error == "invalid_auth" ||
			c.data.Error == "account_inactive" {
			var msg Message
			msg.User = User{}
			msg.Channel = Channel{}

			err := c.sendEvent("disable_bot", &msg, "", "", "")
			if err != nil {
				return err
			}
		}

		log.WithFields(log.Fields{
			"team":  c.TeamId,
			"error": c.data.Error,
		}).Error("starting slack client")

		return fmt.Errorf(fmt.Sprintf("error connecting to slack websocket server: %s", c.data.Error))
	}

	return nil
}
Beispiel #4
0
// startReadFromRedisPubSubLoop is the method invoked by InitClients that listens for new
// clients that need to be started via a Redis Pubsub channel.
func startReadFromRedisPubSubLoop() {
	redisClient := redisclient.Client()

	pubsub := redisClient.PubSub()
	defer pubsub.Close()

	pubsubChannel := os.Getenv("RELAX_BOTS_PUBSUB")
	err := pubsub.Subscribe(pubsubChannel)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("error subscribing to redis pubsub")
		return
	}

	for {
		msgi, err := pubsub.ReceiveTimeout(100 * time.Millisecond)
		if err != nil {
			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
				continue
			}
		} else {
			switch msg := msgi.(type) {
			case *redis.Message:
				if msg.Channel != pubsubChannel {
					break
				}

				payload := msg.Payload
				var cmd Command

				if err := json.Unmarshal([]byte(payload), &cmd); err != nil {
					break
				}

				switch cmd.Type {
				case "message":
					shouldSend := true
					var key string
					var c *Client

					if cmd.TeamId == "" {
						break
					}
					// if Id is not present, then populate an ID
					if cmd.Id == "" {
						cmd.Id = fmt.Sprintf("%d", time.Now().Nanosecond())
					}

					if cmd.Namespace == "" {
						key = cmd.TeamId
					} else {
						key = fmt.Sprintf("%s-%s", cmd.Namespace, cmd.TeamId)
					}

					if _c, ok := Clients.Get(key); ok {
						c = _c.(*Client)
					}

					if c != nil && c.conn != nil {
						key := fmt.Sprintf("send_slack_message:%s", cmd.Id)
						boolCmd := redisClient.HSetNX(os.Getenv("RELAX_MUTEX_KEY"), key, "ok")

						if boolCmd != nil {
							shouldSend = boolCmd.Val()
						}

						if shouldSend {
							c.conn.WriteMessage(websocket.TextMessage, []byte(cmd.Payload))
							log.WithFields(log.Fields{
								"team":       cmd.TeamId,
								"command_id": cmd.Id,
							}).Debug("sent message to slack")
						} else {
							log.WithFields(log.Fields{
								"team":       cmd.TeamId,
								"command_id": cmd.Id,
							}).Debug("ignoring, not sending message to slack")
						}
					}

				case "team_added":
					var key string
					var c *Client

					if cmd.TeamId == "" {
						break
					}
					if cmd.Namespace == "" {
						key = cmd.TeamId
					} else {
						key = fmt.Sprintf("%s-%s", cmd.Namespace, cmd.TeamId)
					}

					result := redisClient.HGet(os.Getenv("RELAX_BOTS_KEY"), key)
					if result == nil {
						break
					}
					val := result.Val()
					if _c, ok := Clients.Get(key); ok {
						c = _c.(*Client)
					}

					if c != nil {
						err := c.Stop()
						if err != nil {
							log.WithFields(log.Fields{
								"team":  cmd.TeamId,
								"error": err,
							}).Error("closing websocket connection")
						}
					}

					c, err := NewClient(val)
					if err == nil {
						c.LoginAndStart()
					} else {
						log.WithFields(log.Fields{
							"team":  cmd.TeamId,
							"error": err,
						}).Error("starting client")
					}

				case "team_removed":
					var key string
					var c *Client

					if cmd.TeamId == "" {
						break
					}
					if cmd.Namespace == "" {
						key = cmd.TeamId
					} else {
						key = fmt.Sprintf("%s-%s", cmd.Namespace, cmd.TeamId)
					}

					result := redisClient.HGet(os.Getenv("RELAX_BOTS_KEY"), key)
					if result == nil {
						break
					}
					if _c, ok := Clients.Get(key); ok {
						c = _c.(*Client)
					}

					if c != nil {
						err := c.Stop()
						if err != nil {
							log.WithFields(log.Fields{
								"team":  cmd.TeamId,
								"error": err,
							}).Error("closing websocket connection")
						}

						Clients.Remove(key)
					}
				}
			}
		}
	}
}
Beispiel #5
0
// Start starts a websocket connection to Slack's servers and starts listening for messages
// If it detects an "invalid_auth" or "inactive_account" message, it means that the token provided by the user
// has expired or is incorrect and so it sends a "disable_bot" event back to the user
// so that they can take remedial action.
func (c *Client) Start() error {
	var key string

	// Make connection to redis now
	c.redisClient = redisclient.Client()

	if c.data.Ok == true {
		conn, _, err := websocket.DefaultDialer.Dial(c.data.Url, http.Header{})
		if err != nil {
			log.WithFields(log.Fields{
				"team":  c.TeamId,
				"error": err,
			}).Error("dialing connection to Slack websocket")

			return err
		} else {
			c.conn = conn
		}

		c.pingTicker = time.NewTicker(time.Millisecond * 5000)
		go c.startReadFromSlackLoop()
		go c.startPingPump()
	} else {
		// Bot has been disabled by the user,
		// so we need to mark it as disabled
		if c.data.Error == "invalid_auth" ||
			c.data.Error == "account_inactive" {
			var msg Message
			msg.User = User{}
			msg.Channel = Channel{}

			err := c.sendEvent("disable_bot", &msg, "", "", "")
			if err != nil {
				return err
			}
		} else if c.data.Error == "migration_in_progress" {
			go c.LoginAndStart()
			return nil
		}

		log.WithFields(log.Fields{
			"team":  c.TeamId,
			"error": c.data.Error,
		}).Error("starting slack client")

		return fmt.Errorf(fmt.Sprintf("error connecting to slack websocket server: %s", c.data.Error))
	}

	// This serves no real purpose other than to let tests know that a certain client has been initialized
	c.redisClient.HSet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot-%s-started", c.TeamId), fmt.Sprintf("%d", time.Now().Nanosecond()))

	if c.Namespace == "" {
		key = c.TeamId
	} else {
		key = fmt.Sprintf("%s-%s", c.Namespace, c.TeamId)
	}

	Clients.Set(key, c)

	return nil
}
Beispiel #6
0
// startReadFromRedisPubSubLoop is the method invoked by InitClients that listens for new
// clients that need to be started via a Redis Pubsub channel.
func startReadFromRedisPubSubLoop() {
	redisClient := redisclient.Client()

	pubsub := redisClient.PubSub()
	defer pubsub.Close()

	pubsubChannel := os.Getenv("RELAX_BOTS_PUBSUB")
	err := pubsub.Subscribe(pubsubChannel)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("error subscribing to redis pubsub")
		return
	}

	for {
		msgi, err := pubsub.ReceiveTimeout(100 * time.Millisecond)
		if err != nil {
			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
				continue
			}
		} else {
			switch msg := msgi.(type) {
			case *redis.Message:
				if msg.Channel != pubsubChannel {
					break
				}

				payload := msg.Payload
				var cmd Command

				if err := json.Unmarshal([]byte(payload), &cmd); err != nil {
					break
				}

				switch cmd.Type {
				case "team_added":
					if cmd.TeamId == "" {
						break
					}
					result := redisClient.HGet(os.Getenv("RELAX_BOTS_KEY"), cmd.TeamId)
					if result == nil {
						break
					}
					val := result.Val()
					c := Clients[cmd.TeamId]

					if c != nil {
						err := c.Stop()
						if err != nil {
							log.WithFields(log.Fields{
								"team":  cmd.TeamId,
								"error": err,
							}).Error("closing websocket connection")
						}
					}

					c, err := NewClient(val)
					if err == nil {
						c.LoginAndStart()
						Clients[cmd.TeamId] = c
					} else {
						log.WithFields(log.Fields{
							"team":  cmd.TeamId,
							"error": err,
						}).Error("starting client")
					}
				}
			}
		}
	}
}