diff --git a/modules/game/src/main/actorApi.scala b/modules/game/src/main/actorApi.scala index 8307e2dfbd..148ea94e19 100644 --- a/modules/game/src/main/actorApi.scala +++ b/modules/game/src/main/actorApi.scala @@ -17,4 +17,6 @@ case class InsertGame(game: Game) case class AbortedBy(pov: Pov) +case class CorresAlarmEvent(pov: Pov) + private[game] case object NewCaptcha diff --git a/modules/hub/src/main/actorApi.scala b/modules/hub/src/main/actorApi.scala index f8e33b0c11..8c3add1f45 100644 --- a/modules/hub/src/main/actorApi.scala +++ b/modules/hub/src/main/actorApi.scala @@ -166,7 +166,6 @@ case class MoveEvent( alarmable: Boolean, opponentUserId: Option[String], simulId: Option[String]) -case class CorresAlarmEvent(gameId: String) case class NbRounds(nb: Int) case class Abort(gameId: String, byColor: String) case class Berserk(gameId: String, userId: String) diff --git a/modules/notify/src/main/BSONHandlers.scala b/modules/notify/src/main/BSONHandlers.scala index 37bdb8af9f..16d9df4465 100644 --- a/modules/notify/src/main/BSONHandlers.scala +++ b/modules/notify/src/main/BSONHandlers.scala @@ -55,6 +55,7 @@ private object BSONHandlers { implicit val PlanExpireHandler = Macros.handler[PlanExpire] implicit val RatingRefundHandler = Macros.handler[RatingRefund] + implicit val CorresAlarmHandler = Macros.handler[CorresAlarm] implicit val ColorBSONHandler = new BSONHandler[BSONBoolean, chess.Color] { def read(b: BSONBoolean) = chess.Color(b.value) @@ -80,6 +81,7 @@ private object BSONHandlers { case x: RatingRefund => RatingRefundHandler.write(x) case ReportedBanned => $empty case CoachReview => $empty + case x: CorresAlarm => CorresAlarmHandler.write(x) } } ++ $doc("type" -> notificationContent.key) @@ -115,6 +117,7 @@ private object BSONHandlers { case "ratingRefund" => RatingRefundHandler read reader.doc case "reportedBanned" => ReportedBanned case "coachReview" => CoachReview + case "corresAlarm" => CorresAlarmHandler read reader.doc } def writes(writer: Writer, n: NotificationContent): dsl.Bdoc = writeNotificationContent(n) diff --git a/modules/notify/src/main/Env.scala b/modules/notify/src/main/Env.scala index c347b8e4e1..a7da5759b3 100644 --- a/modules/notify/src/main/Env.scala +++ b/modules/notify/src/main/Env.scala @@ -22,17 +22,25 @@ final class Env( repo = repo) // api actor - system.actorOf(Props(new Actor { + system.lilaBus.subscribe(system.actorOf(Props(new Actor { def receive = { case lila.hub.actorApi.notify.Notified(userId) => api markAllRead Notification.Notifies(userId) + case lila.game.actorApi.CorresAlarmEvent(pov) => pov.player.userId ?? { userId => + api addNotification Notification.make( + Notification.Notifies(userId), + CorresAlarm( + gameId = pov.gameId, + opponent = lila.game.Namer.playerString(pov.opponent)(getLightUser))) + } } - }), name = ActorName) + }), name = ActorName), 'corresAlarm) } object Env { - lazy val current = "notify" boot new Env(db = lila.db.Env.current, + lazy val current = "notify" boot new Env( + db = lila.db.Env.current, config = lila.common.PlayApp loadConfig "notify", getLightUser = lila.user.Env.current.lightUser, system = lila.common.PlayApp.system) diff --git a/modules/notify/src/main/JsonHandlers.scala b/modules/notify/src/main/JsonHandlers.scala index a097e39da6..feaf9873ab 100644 --- a/modules/notify/src/main/JsonHandlers.scala +++ b/modules/notify/src/main/JsonHandlers.scala @@ -46,6 +46,9 @@ final class JSONHandlers( case RatingRefund(perf, points) => Json.obj( "perf" -> perf, "points" -> points) + case CorresAlarm(gameId, opponent) => Json.obj( + "id" -> gameId, + "op" -> opponent) } } diff --git a/modules/notify/src/main/Notification.scala b/modules/notify/src/main/Notification.scala index 9055fe0477..4f8076dab6 100644 --- a/modules/notify/src/main/Notification.scala +++ b/modules/notify/src/main/Notification.scala @@ -35,7 +35,8 @@ object Notification { sealed abstract class NotificationContent(val key: String) -case class MentionedInThread(mentionedBy: MentionedInThread.MentionedBy, +case class MentionedInThread( + mentionedBy: MentionedInThread.MentionedBy, topic: MentionedInThread.Topic, topidId: MentionedInThread.TopicId, category: MentionedInThread.Category, @@ -49,7 +50,8 @@ object MentionedInThread { case class PostId(value: String) extends AnyVal with StringValue } -case class InvitedToStudy(invitedBy: InvitedToStudy.InvitedBy, +case class InvitedToStudy( + invitedBy: InvitedToStudy.InvitedBy, studyName: InvitedToStudy.StudyName, studyId: InvitedToStudy.StudyId) extends NotificationContent("invitedStudy") @@ -122,3 +124,7 @@ case object CoachReview extends NotificationContent("coachReview") case class PlanStart(userId: String) extends NotificationContent("planStart") case class PlanExpire(userId: String) extends NotificationContent("planExpire") + +case class CorresAlarm( + gameId: lila.game.Game.ID, + opponent: String) extends NotificationContent("corresAlarm") diff --git a/modules/push/src/main/Env.scala b/modules/push/src/main/Env.scala index 2d8eebfe7b..acc640ae79 100644 --- a/modules/push/src/main/Env.scala +++ b/modules/push/src/main/Env.scala @@ -47,12 +47,12 @@ final class Env( system.lilaBus.subscribe(system.actorOf(Props(new Actor { import akka.pattern.pipe def receive = { - case lila.game.actorApi.FinishGame(game, _, _) => pushApi finish game - case move: lila.hub.actorApi.round.MoveEvent => pushApi move move - case lila.message.Event.NewMessage(t, p) => pushApi newMessage (t, p) - case lila.challenge.Event.Create(c) => pushApi challengeCreate c - case lila.challenge.Event.Accept(c, joinerId) => pushApi.challengeAccept(c, joinerId) - case lila.hub.actorApi.round.CorresAlarmEvent(id) => pushApi corresAlarm id + case lila.game.actorApi.FinishGame(game, _, _) => pushApi finish game + case move: lila.hub.actorApi.round.MoveEvent => pushApi move move + case lila.message.Event.NewMessage(t, p) => pushApi newMessage (t, p) + case lila.challenge.Event.Create(c) => pushApi challengeCreate c + case lila.challenge.Event.Accept(c, joinerId) => pushApi.challengeAccept(c, joinerId) + case lila.game.actorApi.CorresAlarmEvent(pov) => pushApi corresAlarm pov } })), 'finishGame, 'moveEvent, 'newMessage, 'challenge, 'corresAlarm) } diff --git a/modules/push/src/main/PushApi.scala b/modules/push/src/main/PushApi.scala index 387a312e53..e13daa662f 100644 --- a/modules/push/src/main/PushApi.scala +++ b/modules/push/src/main/PushApi.scala @@ -78,29 +78,23 @@ private final class PushApi( } } - def corresAlarm(gameId: String): Funit = GameRepo game gameId flatMap { - _ ?? { game => - val pov = Pov(game, game.turnColor) - game.player(pov.color).userId ?? { userId => - IfAway(pov) { - pushToAll(userId, _.corresAlarm, PushApi.Data( - title = "Time is almost up!", - body = s"You are about to lose on time against ${opponentName(pov)}", - stacking = Stacking.GameMove, - payload = Json.obj( - "userId" -> userId, - "userData" -> Json.obj( - "type" -> "corresAlarm", - "gameId" -> game.id, - "fullId" -> pov.fullId, - "color" -> pov.color.name, - "fen" -> Forsyth.exportBoard(game.toChess.board), - "lastMove" -> game.castleLastMoveTime.lastMoveString, - "secondsLeft" -> pov.remainingSeconds)))) - } - } + def corresAlarm(pov: Pov): Funit = + pov.player.userId ?? { userId => + pushToAll(userId, _.corresAlarm, PushApi.Data( + title = "Time is almost up!", + body = s"You are about to lose on time against ${opponentName(pov)}", + stacking = Stacking.GameMove, + payload = Json.obj( + "userId" -> userId, + "userData" -> Json.obj( + "type" -> "corresAlarm", + "gameId" -> pov.gameId, + "fullId" -> pov.fullId, + "color" -> pov.color.name, + "fen" -> Forsyth.exportBoard(pov.game.toChess.board), + "lastMove" -> pov.game.castleLastMoveTime.lastMoveString, + "secondsLeft" -> pov.remainingSeconds)))) } - } def newMessage(t: Thread, p: Post): Funit = lightUser(t.senderOf(p)) ?? { sender => diff --git a/modules/round/src/main/CorresAlarm.scala b/modules/round/src/main/CorresAlarm.scala index 7f173b5936..83b1d46594 100644 --- a/modules/round/src/main/CorresAlarm.scala +++ b/modules/round/src/main/CorresAlarm.scala @@ -1,15 +1,21 @@ package lila.round import akka.actor._ +import akka.pattern.ask import org.joda.time.DateTime import play.api.libs.iteratee._ import reactivemongo.api._ import scala.concurrent.duration._ import lila.db.dsl._ -import lila.game.GameRepo +import lila.hub.actorApi.map.Ask +import lila.hub.actorApi.round.IsOnGame +import lila.game.{ GameRepo, Pov } +import makeTimeout.short -private final class CorresAlarm(coll: Coll) extends Actor { +private final class CorresAlarm( + coll: Coll, + roundSocketHub: ActorSelection) extends Actor { object Schedule object Run @@ -45,12 +51,17 @@ private final class CorresAlarm(coll: Coll) extends Actor { )).cursor[Alarm](ReadPreference.secondaryPreferred) .enumerator(100, Cursor.ContOnError()) .|>>>(Iteratee.foldM[Alarm, Int](0) { - case (count, alarm) => { - context.system.lilaBus.publish( - lila.hub.actorApi.round.CorresAlarmEvent(alarm._id), - 'corresAlarm) - coll.remove($id(alarm._id)) - } inject (count + 1) + case (count, alarm) => GameRepo.game(alarm._id).flatMap { + _ ?? { game => + val pov = Pov(game, game.turnColor) + roundSocketHub ? Ask(pov.gameId, IsOnGame(pov.color)) mapTo manifest[Boolean] addEffect { + case true => // already looking at the game + case false => context.system.lilaBus.publish( + lila.game.actorApi.CorresAlarmEvent(pov), + 'corresAlarm) + } + } + } >> coll.remove($id(alarm._id)) inject (count + 1) }) .chronometer.mon(_.round.alarm.time).result .addEffect(c => lila.mon.round.alarm.count(c)) diff --git a/modules/round/src/main/Env.scala b/modules/round/src/main/Env.scala index 63249e2ccb..564f4d07e3 100644 --- a/modules/round/src/main/Env.scala +++ b/modules/round/src/main/Env.scala @@ -193,11 +193,11 @@ final class Env( scheduler.message(2.1 seconds)(roundMap -> actorApi.GetNbRounds) system.actorOf( - Props(classOf[Titivate], roundMap, hub.actor.bookmark, hub.actor.chat), + Props(new Titivate(roundMap, hub.actor.bookmark, hub.actor.chat)), name = "titivate") system.lilaBus.subscribe(system.actorOf( - Props(classOf[CorresAlarm], db(CollectionAlarm)), + Props(new CorresAlarm(db(CollectionAlarm), hub.socket.round)), name = "corres-alarm"), 'moveEvent, 'finishGame) lazy val takebacker = new Takebacker( diff --git a/project/Build.scala b/project/Build.scala index 3867ca92ab..6d2439aa5d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -327,7 +327,7 @@ object ApplicationBuild extends Build { reactivemongo.driver, reactivemongo.iteratees) ) - lazy val notifyModule = project("notify", Seq(common, db, user, hub, relation)).settings( + lazy val notifyModule = project("notify", Seq(common, db, game, user, hub, relation)).settings( libraryDependencies ++= provided(play.api, reactivemongo.driver) ) diff --git a/ui/notify/src/view.js b/ui/notify/src/view.js index c23d185fd5..8b03d9499d 100644 --- a/ui/notify/src/view.js +++ b/ui/notify/src/view.js @@ -255,6 +255,22 @@ var handlers = { return 'Refund: ' + n.content.points + ' ' + n.content.perf + ' rating points.' } }, + corresAlarm: { + html: function(notification) { + var url = '/' + notification.content.id; + + return genericNotification(notification, url, ';', [ + m('span', [ + m('strong', 'Time is almost up!'), + drawTime(notification) + ]), + m('span', 'Game vs ' + notification.content.op) + ]); + }, + text: function(n) { + return 'Time is almost up!'; + } + }, }; function drawNotification(notification) {