Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message thread #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ class IncomingMessageProcessor(eventBus: MessageEventBus) extends Actor with Act
val incomingMessage: IncomingMessage = mType match {
case MessageType("hello", _) => Hello
case MessageType("pong", _) => Pong
case MessageType("message", None) => s.parseJson.convertTo[BaseMessage]
case MessageType("message", _) => s.parseJson.convertTo[BaseMessage]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this case will match also Some, unless the order is reversed.

case MessageType("message", Some("message_replied")) =>
s.parseJson.convertTo[MessageThread]
case _ =>
UndefinedMessage(s)
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/io/scalac/slack/OutgoingMessageProcessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class OutgoingMessageProcessor(wsActor: ActorRef, eventBus: MessageEventBus) ext
case msg: OutboundMessage =>
wsActor ! WebSocket.Send(msg.toJson)

case msg: ThreadedOutboundMessage =>
wsActor ! WebSocket.Send(msg.toJson)

case ignored => //nothing else

}
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/io/scalac/slack/api/ApiActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ class ApiActor extends Actor with ActorLogging {
log.debug("chat.postMessage requested")

val attachments = msg.elements.filter(_.isValid).map(_.toJson).mkString("[", ",", "]")
val params = Map("token" -> Config.apiKey.key, "channel" -> msg.channel, "as_user" -> "true", "attachments" -> attachments)
val params = msg.ts match {
case Some(_) => Map("token" -> Config.apiKey.key, "channel" -> msg.channel, "as_user" -> "true", "attachments" -> attachments, "thread_ts" -> msg.ts.get)
case None => Map("token" -> Config.apiKey.key, "channel" -> msg.channel, "as_user" -> "true", "attachments" -> attachments)
}

SlackApiClient.post[ChatPostMessageResponse]("chat.postMessage", params) onComplete {
case Success(res) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CommandsRecognizerBot(override val bus: MessageEventBus) extends IncomingM

def receive: Receive = {

case bm@BaseMessage(text, channel, user, dateTime, edited) =>
case bm@BaseMessage(text, channel, user, dateTime, Some(thread_ts),edited) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we get None in place of thread_ts?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before edited

//COMMAND links list with bot's nam jack can be called:
// jack link list
// jack: link list
Expand Down
67 changes: 64 additions & 3 deletions src/main/scala/io/scalac/slack/common/MessageJsonProtocol.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,84 @@ object MessageJsonProtocol extends DefaultJsonProtocol {

def read(value: JsValue) = {

value.asJsObject.getFields("text", "channel", "user", "ts", "edited") match {
value.asJsObject.getFields("text", "channel", "user", "ts", "edited", "thread_ts") match {

case Seq(JsString(text), JsString(channel), JsString(user), JsString(ts)) =>
BaseMessage(text, channel, user, ts, edited = false)

case Seq(JsString(text), JsString(channel), JsString(user), JsString(ts), JsObject(edited)) =>

BaseMessage(text, channel, user, ts, edited = true)

case Seq(JsString(text), JsString(channel), JsString(user), JsString(ts), JsString(thread_ts)) =>
BaseMessage(text, channel, user, ts, Some(thread_ts))

case Seq(JsString(text), JsString(channel), JsString(user), JsString(ts), JsString(thread_ts), JsObject(edited)) =>
BaseMessage(text, channel, user, ts, Some(thread_ts), edited = true)

case _ =>
throw new DeserializationException("BaseMessage expected")
}
}
}

implicit object MessageThreadJsonReader extends RootJsonReader[MessageThread] {
def read(value: JsValue) = {
value.asJsObject.getFields("message","hidden","channel", "event_ts", "ts") match {
case Vector(message,JsTrue, JsString(channel), JsString(event_ts), JsString(ts)) =>
MessageThread(message.convertTo[Message], true, channel,event_ts,ts)

implicit val messageTypeFormat = jsonFormat(MessageType, "type", "subtype")
case _ =>
throw new DeserializationException("MessageThread expected")
}
}
}

implicit object MessageJsonReader extends RootJsonReader[Message] {

def read(value: JsValue) = {
value.asJsObject.getFields("user", "text", "thread_ts", "reply_count", "replies", "unread_count", "ts") match {
case Vector(JsString(user), JsString(text), JsString(thread_ts), JsNumber(reply_count), replies, JsNumber(unread_count),JsString(ts)) =>
Message(user, text, thread_ts, reply_count, replies.convertTo[Replies], unread_count, ts)

case _ =>
throw new DeserializationException("Message expected")
}
}
}

implicit object RepliesJsonReader extends RootJsonFormat[Replies] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a Format? Would Reader be enough?


def read(value: JsValue) = {
println(value.getClass)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove ;)

value match {
case JsArray(replies) =>
val r = replies.map{ x =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a space before {. Also, since x and r aren't very descriptive, we might as well do

Replies {
  replies.map(_.convertTo[Reply])
}

x.convertTo[Reply]
}
Replies(r)

case _ =>
throw new DeserializationException("Reply expected")
}
}

override def write(obj: Replies): JsValue = serializationError("not supported")
}

implicit object ReplyJsonReader extends RootJsonFormat[Reply] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question about format vs reader


def read(value: JsValue) = {
value.asJsObject.getFields("user", "ts") match {
case Seq(JsString(user), JsString(ts)) =>
Reply(user, ts)

case _ =>
throw new DeserializationException("Reply expected")
}
}

override def write(obj: Reply): JsValue = serializationError("not supported")
}

implicit val messageTypeFormat = jsonFormat(MessageType, "type", "subtype")
}
35 changes: 29 additions & 6 deletions src/main/scala/io/scalac/slack/common/Messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,19 @@ case object Hello extends IncomingMessage
* @param user ID of message author
* @param ts unique timestamp
*/
case class BaseMessage(text: String, channel: String, user: String, ts: String, edited: Boolean = false) extends IncomingMessage
case class BaseMessage(text: String, channel: String, user: String, ts: String, thread_ts: Option[String] = None, edited: Boolean = false) extends IncomingMessage

//Message thread
case class MessageThread(message: Message, hidden: Boolean, channel: String, event_ts: String, ts: String) extends IncomingMessage

//Message
case class Message(user: String, text: String, thread_ts: String, reply_count: BigDecimal, replies: Replies, unread_count: BigDecimal, ts: String) extends IncomingMessage

//Reply
case class Reply(user: String, ts: String) extends IncomingMessage

//Replies
case class Replies(replies: Seq[Reply]) extends IncomingMessage

//user issued command to bot
case class Command(command: String, params: List[String], underlying: BaseMessage) extends IncomingMessage
Expand Down Expand Up @@ -53,12 +65,23 @@ case class OutboundMessage(channel: String, text: String) extends OutgoingMessag
override def toJson =
s"""{
|"id": ${MessageCounter.next},
|"type": "message",
|"channel": "$channel",
|"text": "$text"
|}""".stripMargin
|"type": "message",
|"channel": "$channel",
|"text": "$text"
|}""".stripMargin
}

//todo: Fold this into Outbound Message
case class ThreadedOutboundMessage(channel: String, text: String, ts: String) extends OutgoingMessage {
override def toJson =
s"""{
|"id": ${MessageCounter.next},
|"type": "message",
|"channel": "$channel",
|"text": "$text",
|"thread_ts": "$ts"
|}""".stripMargin
}
sealed trait RichMessageElement

case class Text(value: String) extends RichMessageElement
Expand All @@ -79,7 +102,7 @@ object Color {

case class ImageUrl(url: String) extends RichMessageElement

case class RichOutboundMessage(channel: String, elements: List[Attachment]) extends MessageEvent
case class RichOutboundMessage(channel: String, elements: List[Attachment], ts: Option[String] = None) extends MessageEvent

case class Attachment(text: Option[String] = None, pretext: Option[String] = None, fields: Option[List[Field]] = None, title: Option[String] = None, title_link: Option[String] = None, color: Option[String] = None, image_url: Option[String] = None) {
def isValid = text.isDefined || pretext.isDefined || title.isDefined || (fields.isDefined && fields.get.nonEmpty)
Expand Down
1 change: 0 additions & 1 deletion src/test/scala/io/scalac/slack/UnmarshallerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -718,5 +718,4 @@ class UnmarshallerTest extends FunSuite with Matchers {
im.id should equal("D03DQKG1C")
im.userId should equal("U03DN1GTQ")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.scalac.slack.common
import org.scalatest.{FunSuite, Matchers}
import spray.json._

import scala.collection.Seq


/**
* Created on 10.02.15 17:37
Expand All @@ -19,4 +21,98 @@ class MessageJsonProtocolTest extends FunSuite with Matchers {
hello.subType should be(None)
}

test("Reply") {

val replyJson = /*language=json*/ """{
| "user": "U04PFEX3D",
| "ts": "1501038656.314433"
|}""".stripMargin
val reply = replyJson.parseJson.convertTo[Reply]

reply.user should equal ("U04PFEX3D")
reply.ts should equal ("1501038656.314433")
}

test("Replies") {

val repliesJson = /*language=json*/ """[{
| "user": "U04PFEX3D",
| "ts": "1501038656.314433"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038707.323684"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038963.368899"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038994.374530"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501040179.578238"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501130090.866379"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501130196.883555"
|}]""".stripMargin
val replies = repliesJson.parseJson.convertTo[Replies]

replies.replies.size should equal (7)
replies.replies.head.user should equal ("U04PFEX3D")
replies.replies.head.ts should equal ("1501038656.314433")
replies.replies(3).user should equal ("U04PFEX3D")
replies.replies(3).ts should equal ("1501038994.374530")

assert(replies.isInstanceOf[Replies])
}

test("Message object") {
/*language=JSON*/
val messageString = """{
| "type": "message",
| "user": "U04PFEX3D",
| "text": "can\u2019t",
| "thread_ts": "1500905121.099692",
| "reply_count": 7,
| "replies": [{
| "user": "U04PFEX3D",
| "ts": "1501038656.314433"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038707.323684"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038963.368899"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501038994.374530"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501040179.578238"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501130090.866379"
| }, {
| "user": "U04PFEX3D",
| "ts": "1501130196.883555"
| }],
| "unread_count": 7,
| "ts": "1500905121.099692"
|}""".stripMargin

val message = messageString.parseJson.convertTo[Message]
message.user should equal("U04PFEX3D")
message.text should equal("can\u2019t")
message.thread_ts should equal("1500905121.099692")
message.reply_count should equal(7)
message.unread_count should equal(7)
message.ts should equal("1500905121.099692")
message.replies.replies.size should equal(7)

assert(message.replies.isInstanceOf[Replies])
assert(message.replies.replies.head.isInstanceOf[Reply])
}

}