From 8535cd22b1dd88020cbd38102ffe1a4fc15cc94b Mon Sep 17 00:00:00 2001 From: Eduard Gomolyako Date: Tue, 28 Oct 2014 19:37:41 +0300 Subject: [PATCH] Now Bot keeps channel purpose as a reminder at last message in channel. You can keep skype:chat link there. --- src/main/scala/com/maqdev/SlackApi.scala | 81 ++++++++++++++----- .../com/maqdev/SlackReportMessagesActor.scala | 6 +- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/main/scala/com/maqdev/SlackApi.scala b/src/main/scala/com/maqdev/SlackApi.scala index 03a735c..2507e11 100644 --- a/src/main/scala/com/maqdev/SlackApi.scala +++ b/src/main/scala/com/maqdev/SlackApi.scala @@ -2,13 +2,14 @@ package com.maqdev import akka.actor.ActorSystem import com.typesafe.config.ConfigFactory -import play.api.libs.json.{JsValue, Json} +import play.api.libs.json.{JsValue, Json, Reads} import spray.client.pipelining._ import spray.http.Uri import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.util.matching._ +import java.util.concurrent.ConcurrentHashMap object SlackApi { import scala.concurrent.ExecutionContext.Implicits.global @@ -23,11 +24,21 @@ object SlackApi { val htmlEntities = Map("lt" → "<", "gt" → ">", "amp" → "&", "quot" → "\"", "apos" → "'") map { case (s, c) ⇒ c → ("&%s;" format s) } + var slackChannels = new ConcurrentHashMap[String, (String, String)](); + def postMessageToSlack(m: SkypeMessage, slackChannelId: String) = { val strippedMessage = """<[^< ][^>]+?>""".r replaceAllIn (m.message replaceAllLiterally (" + slackChannels.put(slackChannelId, (result.channel.purpose.value, "")) + } + } + + slackRequest[SlackChatResult]("chat.postMessage", Map( "channel" → slackChannelId, "username" → s"${m.authorName} @ Skype", "icon_url" → s"http://api.skype.com/users/${m.author}/profile/avatar", @@ -36,44 +47,78 @@ object SlackApi { "unfurl_links" → s"true", "unfurl_media" → s"true", "text" → sanitizedMessage - )) - } + )) map { response ⇒ + Option(slackChannels.get(response.channel)) foreach { case (uri, ts) ⇒ + if (ts.nonEmpty) { + slackRequest[SlackChatResult]("chat.delete", Map( + "channel" → response.channel, + "ts" → ts + )) + } - case class SlackChannel(id: String, name: String) - def getChannelsFromSlack: Future[List[SlackChannel]] = { - implicit val slackChannelReads = Json.reads[SlackChannel] - - slackRequest("channels.list") map { - json ⇒ - val channelsJson = json \ "channels" - Json.fromJson[List[SlackChannel]](channelsJson).get + if (uri.nonEmpty) { + slackRequest[SlackChatResult]("chat.postMessage", Map( + "channel" → response.channel, + "username" → "SkypeBot", + "icon_url" → "http://en.gravatar.com/userimage/6208733/1129353ced07ece051aefd7e63d8b2f8.jpg", + "text" → uri + )) map { result => + slackChannels.put(slackChannelId, (uri, result.ts)) + } + } + } } } - def getChannelsFromSlackSync: List[SlackChannel] = { + def getChannelsFromSlack: Future[List[SlackChannelInfo]] = { + slackRequest[SlackChannelsListResult]("channels.list") map (_.channels) + } + + def getChannelsFromSlackSync: List[SlackChannelInfo] = { // todo: remove sync version Await.result(getChannelsFromSlack, 30 seconds) } class SlackException(json: JsValue) extends Exception("Slack call failed: " + json.toString()) - case class SlackRequestResult(ok: Boolean) - def slackRequest(method: String, data: Map[String,String] = Map()): Future[JsValue] = { - implicit val slackRequestResultReads = Json.reads[SlackRequestResult] + trait SlackRequestResult { + val ok: Boolean = false + } + + case class SlackChannelPurpose(value: String) + case class SlackChannelInfo(id: String, name: String, purpose: SlackChannelPurpose) + + case class SlackChannelsListResult(channels: List[SlackChannelInfo]) extends SlackRequestResult + case class SlackChannelsInfoResult(channel: SlackChannelInfo) extends SlackRequestResult + case class SlackChatResult(channel: String, ts: String) extends SlackRequestResult + + implicit val slackChannelPurposeReads = Json.reads[SlackChannelPurpose] + implicit val slackChannelInfo = Json.reads[SlackChannelInfo] + + implicit val slackChannelsListResultReads = Json.reads[SlackChannelsListResult] + implicit val slackChannelsInfoResultReads = Json.reads[SlackChannelsInfoResult] + implicit val slackChatResultReads = Json.reads[SlackChatResult] + + def slackRequest[T <: SlackRequestResult : Reads](method: String, data: Map[String,String] = Map()): Future[T] = { val uri = Uri(s"https://slack.com/api/$method").withQuery( Map("token" → slackAuthToken) ++ data ) + log.debug("--> slack: {}", uri) + val pipeline = sendReceive ~> unmarshal[String] + pipeline { Get(uri) } map { s ⇒ log.debug("<-- slack: {} ", s) + val json = Json.parse(s) - val r = Json.fromJson[SlackRequestResult](json).get + val r = Json.fromJson[T](json).get + if (r.ok) { - json + r } else { throw new SlackException(json) diff --git a/src/main/scala/com/maqdev/SlackReportMessagesActor.scala b/src/main/scala/com/maqdev/SlackReportMessagesActor.scala index 775c22e..a718a30 100644 --- a/src/main/scala/com/maqdev/SlackReportMessagesActor.scala +++ b/src/main/scala/com/maqdev/SlackReportMessagesActor.scala @@ -4,7 +4,7 @@ import java.io.File import akka.actor.Actor import akka.event.Logging -import com.maqdev.SlackApi.SlackChannel +import com.maqdev.SlackApi.SlackChannelInfo import com.typesafe.config.ConfigFactory import scala.collection.mutable @@ -50,7 +50,7 @@ class SlackReportMessagesActor extends Actor { } defaultChannelId = SlackApi.getChannelsFromSlackSync.collect { - case x: SlackChannel if x.name.compareToIgnoreCase(defaultChannel) == 0 ⇒ x.id + case x: SlackChannelInfo if x.name.compareToIgnoreCase(defaultChannel) == 0 ⇒ x.id }.headOption } @@ -133,7 +133,7 @@ class SlackReportMessagesActor extends Actor { } } - def channels: Future[(List[SkypeChat], List[SlackChannel])] = { + def channels: Future[(List[SkypeChat], List[SlackChannelInfo])] = { import context.dispatcher val f = SlackApi.getChannelsFromSlack val db = new SkypeDb(dbPath)