lila/modules/playban/src/main/PlaybanApi.scala

186 lines
6.1 KiB
Scala
Raw Normal View History

2015-04-25 12:48:13 -06:00
package lila.playban
import reactivemongo.bson._
import chess.{ Status, Color }
2015-04-25 12:48:13 -06:00
import lila.db.BSON._
import lila.db.dsl._
2015-04-25 15:06:44 -06:00
import lila.game.{ Pov, Game, Player, Source }
import lila.user.{ User, UserRepo }
2015-04-25 12:48:13 -06:00
2015-04-27 06:07:35 -06:00
final class PlaybanApi(
coll: Coll,
2017-09-11 23:09:46 -06:00
sandbag: SandbagWatch,
feedback: PlaybanFeedback,
2017-10-19 22:02:55 -06:00
bus: lila.common.Bus
) {
2015-04-25 12:48:13 -06:00
import lila.db.BSON.BSONJodaDateTimeHandler
import reactivemongo.bson.Macros
private implicit val OutcomeBSONHandler = new BSONHandler[BSONInteger, Outcome] {
def read(bsonInt: BSONInteger): Outcome = Outcome(bsonInt.value) err s"No such playban outcome: ${bsonInt.value}"
def write(x: Outcome) = BSONInteger(x.id)
}
2015-04-25 15:06:44 -06:00
private implicit val banBSONHandler = Macros.handler[TempBan]
2015-04-25 12:48:13 -06:00
private implicit val UserRecordBSONHandler = Macros.handler[UserRecord]
2015-04-25 15:06:44 -06:00
private case class Blame(player: Player, outcome: Outcome)
2017-09-11 23:09:46 -06:00
private val blameableSources: Set[Source] = Set(Source.Lobby, Source.Pool, Source.Tournament)
2016-11-29 18:07:23 -07:00
private def blameable(game: Game): Fu[Boolean] =
(game.source.exists(s => blameableSources(s)) && game.hasClock) ?? {
if (game.rated) fuccess(true)
else UserRepo.containsEngine(game.userIds) map (!_)
}
private def IfBlameable[A: ornicar.scalalib.Zero](game: Game)(f: => Fu[A]): Fu[A] =
blameable(game) flatMap { _ ?? f }
2015-04-25 15:06:44 -06:00
def abort(pov: Pov, isOnGame: Set[Color]): Funit = IfBlameable(pov.game) {
pov.player.userId.ifTrue(isOnGame(pov.opponent.color)) ?? { userId =>
save(Outcome.Abort, userId) >>- feedback.abort(pov)
}
}
def noStart(pov: Pov): Funit = IfBlameable(pov.game) {
pov.player.userId ?? { userId =>
save(Outcome.NoPlay, userId) >>- feedback.noStart(pov)
}
2015-04-26 05:04:22 -06:00
}
2015-04-25 15:06:44 -06:00
2017-09-11 23:09:46 -06:00
def rageQuit(game: Game, quitterColor: Color): Funit =
sandbag(game, quitterColor) >> IfBlameable(game) {
game.player(quitterColor).userId ?? { userId =>
save(Outcome.RageQuit, userId) >>- feedback.rageQuit(Pov(game, quitterColor))
}
2017-09-11 23:09:46 -06:00
}
2015-04-25 15:06:44 -06:00
2017-10-18 13:02:59 -06:00
def flag(game: Game, flaggerColor: Color): Funit = {
def unreasonableTime = game.clock map { c =>
(c.estimateTotalSeconds / 12) atLeast 15 atMost (3 * 60)
2017-10-18 13:02:59 -06:00
}
// flagged after waiting a long time
def sitting = for {
userId <- game.player(flaggerColor).userId
seconds = nowSeconds - game.movedAt.getSeconds
limit <- unreasonableTime
if seconds >= limit
} yield save(Outcome.Sitting, userId) >>- feedback.sitting(Pov(game, flaggerColor))
2017-10-18 13:02:59 -06:00
// flagged after waiting a short time;
// but the previous move used a long time.
// assumes game was already checked for sitting
def sitMoving = for {
userId <- game.player(flaggerColor).userId
movetimes <- game moveTimes flaggerColor
lastMovetime <- movetimes.lastOption
limit <- unreasonableTime
if lastMovetime.toSeconds >= limit
} yield save(Outcome.SitMoving, userId) >>- feedback.sitting(Pov(game, flaggerColor))
2017-10-18 13:02:59 -06:00
2017-10-18 17:16:52 -06:00
sandbag(game, flaggerColor) flatMap { isSandbag =>
IfBlameable(game) {
sitting orElse
sitMoving getOrElse
goodOrSandbag(game, flaggerColor, isSandbag)
2017-10-18 13:02:59 -06:00
}
2017-09-11 23:09:46 -06:00
}
2017-10-18 13:02:59 -06:00
}
2017-09-11 23:09:46 -06:00
def other(game: Game, status: Status.type => Status, winner: Option[Color]): Funit =
2017-10-18 17:16:52 -06:00
winner.?? { w => sandbag(game, !w) } flatMap { isSandbag =>
IfBlameable(game) {
~(for {
2017-10-18 17:16:52 -06:00
w <- winner
loserId <- game.player(!w).userId
} yield {
if (Status.NoStart is status) save(Outcome.NoPlay, loserId) >>- feedback.noStart(Pov(game, !w))
else goodOrSandbag(game, !w, isSandbag)
})
2017-10-18 17:16:52 -06:00
}
2017-09-11 23:09:46 -06:00
}
2015-04-25 15:06:44 -06:00
2017-10-18 17:16:52 -06:00
private def goodOrSandbag(game: Game, color: Color, isSandbag: Boolean): Funit =
game.player(color).userId ?? { userId =>
if (isSandbag) feedback.sandbag(Pov(game, color))
save(if (isSandbag) Outcome.Sandbag else Outcome.Good, userId)
2017-10-18 17:16:52 -06:00
}
def currentBan(userId: User.ID): Fu[Option[TempBan]] = coll.find(
$doc("_id" -> userId, "b.0" $exists true),
$doc("_id" -> false, "b" -> $doc("$slice" -> -1))
).uno[Bdoc].map {
2015-04-26 04:08:13 -06:00
_.flatMap(_.getAs[List[TempBan]]("b")).??(_.find(_.inEffect))
}
2015-04-25 15:06:44 -06:00
def hasCurrentBan(userId: User.ID): Fu[Boolean] = currentBan(userId).map(_.isDefined)
2017-08-04 08:12:21 -06:00
def completionRate(userId: User.ID): Fu[Option[Double]] =
coll.primitiveOne[List[Outcome]]($id(userId), "o").map(~_) map { outcomes =>
outcomes.collect {
case Outcome.RageQuit | Outcome.Sitting | Outcome.NoPlay => false
case Outcome.Good => true
} match {
case c if c.size >= 5 => Some(c.count(identity).toDouble / c.size)
case _ => none
}
2015-04-25 15:06:44 -06:00
}
def bans(userId: User.ID): Fu[List[TempBan]] =
coll.primitiveOne[List[TempBan]]($doc("_id" -> userId, "b.0" $exists true), "b").map(~_)
def bans(userIds: List[User.ID]): Fu[Map[User.ID, Int]] = coll.find(
$inIds(userIds),
$doc("b" -> true)
).cursor[Bdoc]().gather[List]().map {
2015-04-25 23:44:35 -06:00
_.flatMap { obj =>
obj.getAs[User.ID]("_id") flatMap { id =>
obj.getAs[Barr]("b") map { id -> _.stream.size }
2015-04-25 23:44:35 -06:00
}
}(scala.collection.breakOut)
2015-04-25 23:44:35 -06:00
}
private def save(outcome: Outcome, userId: User.ID): Funit = {
2017-10-22 17:14:35 -06:00
lila.mon.playban.outcome(outcome.key)()
2015-07-13 09:32:14 -06:00
coll.findAndUpdate(
selector = $id(userId),
update = $doc("$push" -> $doc(
"o" -> $doc(
2015-07-13 09:32:14 -06:00
"$each" -> List(outcome),
"$slice" -> -30
)
2015-07-13 09:32:14 -06:00
)),
fetchNewObject = true,
upsert = true
).map(_.value)
2015-04-25 12:48:13 -06:00
} map2 UserRecordBSONHandler.read flatMap {
case None => fufail(s"can't find record for user $userId")
case _ if outcome == Outcome.Good => funit
2015-04-25 15:06:44 -06:00
case Some(record) => legiferate(record)
2016-03-20 03:31:09 -06:00
} logFailure lila.log("playban")
2015-04-25 15:06:44 -06:00
2017-10-19 22:02:55 -06:00
private def legiferate(record: UserRecord): Funit = {
record.bannable ?? { ban =>
2017-11-19 15:42:09 -07:00
(!record.banInEffect) ?? {
lila.mon.playban.ban.count()
lila.mon.playban.ban.mins(ban.mins)
bus.publish(lila.hub.actorApi.playban.Playban(record.userId, ban.mins), 'playban)
coll.update(
$id(record.userId),
$unset("o") ++
$push(
"b" -> $doc(
"$each" -> List(ban),
"$slice" -> -30
)
2017-10-19 22:02:55 -06:00
)
2017-11-19 15:42:09 -07:00
).void
}
2017-10-19 22:02:55 -06:00
}
2015-04-25 15:06:44 -06:00
}
2015-04-25 12:48:13 -06:00
}