Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions src/main/scala/com/maqdev/SlackApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ("<quote", "> <quote"), "")
val sanitizedMessage = htmlEntities.foldLeft(strippedMessage) { case (result, (chr, ent)) ⇒ result replaceAllLiterally (ent, chr) }

slackRequest("chat.postMessage", Map(
if (slackChannels.get(slackChannelId) != null) {
slackRequest[SlackChannelsInfoResult]("channels.info", Map(
"channel" → slackChannelId
)) map { result =>
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",
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/com/maqdev/SlackReportMessagesActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down