2015-04-25 12:48:13 -06:00
|
|
|
package lila.playban
|
|
|
|
|
|
|
|
import reactivemongo.bson._
|
|
|
|
|
2017-06-19 13:39:39 -06:00
|
|
|
import chess.{ Status, Color }
|
2015-04-25 12:48:13 -06:00
|
|
|
import lila.db.BSON._
|
2016-04-02 04:13:25 -06:00
|
|
|
import lila.db.dsl._
|
2015-04-25 15:06:44 -06:00
|
|
|
import lila.game.{ Pov, Game, Player, Source }
|
2016-07-19 13:47:22 -06:00
|
|
|
import lila.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,
|
2017-02-14 08:34:07 -07:00
|
|
|
isRematch: String => Boolean
|
|
|
|
) {
|
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
|
|
|
|
2016-07-19 13:47:22 -06:00
|
|
|
private def blameable(game: Game): Fu[Boolean] =
|
2017-06-19 13:39:39 -06:00
|
|
|
(game.source.exists(s => blameableSources(s)) && game.hasClock && !isRematch(game.id)) ?? {
|
2016-07-19 13:47:22 -06:00
|
|
|
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
|
|
|
|
2016-09-07 11:33:24 -06:00
|
|
|
def abort(pov: Pov, isOnGame: Set[Color]): Funit = IfBlameable(pov.game) {
|
2016-07-19 13:47:22 -06:00
|
|
|
{
|
2016-09-07 11:33:24 -06:00
|
|
|
if (pov.game olderThan 30) pov.game.playerWhoDidNotMove map { Blame(_, Outcome.NoPlay) }
|
2016-07-19 13:47:22 -06:00
|
|
|
else if (pov.game olderThan 15) none
|
2016-09-07 11:33:24 -06:00
|
|
|
else if (isOnGame(pov.opponent.color)) pov.player.some map { Blame(_, Outcome.Abort) }
|
|
|
|
else none
|
2016-07-19 13:47:22 -06:00
|
|
|
} ?? {
|
|
|
|
case Blame(player, outcome) => player.userId.??(save(outcome))
|
|
|
|
}
|
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 ?? save(Outcome.RageQuit)
|
|
|
|
}
|
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 =>
|
2017-10-18 13:03:57 -06:00
|
|
|
(c.estimateTotalSeconds / 8) 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)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
sandbag(game, flaggerColor) >> IfBlameable(game) {
|
|
|
|
goodFinish(game, !flaggerColor) >> { // winner gets a good game result
|
|
|
|
sitting orElse
|
|
|
|
sitMoving getOrElse
|
|
|
|
goodFinish(game, flaggerColor)
|
|
|
|
}
|
2017-09-11 23:09:46 -06:00
|
|
|
}
|
2017-10-18 13:02:59 -06:00
|
|
|
}
|
2016-07-18 02:22:13 -06:00
|
|
|
|
2017-09-11 23:09:46 -06:00
|
|
|
def other(game: Game, status: Status.type => Status, winner: Option[Color]): Funit =
|
|
|
|
winner.?? { w => sandbag(game, !w) } >> IfBlameable(game) {
|
|
|
|
((for {
|
|
|
|
w <- winner
|
|
|
|
loserId <- game.player(!w).userId
|
|
|
|
if Status.NoStart is status
|
|
|
|
} yield List(save(Outcome.NoPlay)(loserId), goodFinish(game, w))) |
|
|
|
|
game.userIds.map(save(Outcome.Good))).sequenceFu.void
|
|
|
|
}
|
2015-04-25 15:06:44 -06:00
|
|
|
|
2017-06-19 13:39:39 -06:00
|
|
|
private def goodFinish(game: Game, color: Color): Funit =
|
|
|
|
~(game.player(color).userId.map(save(Outcome.Good)))
|
|
|
|
|
2015-04-26 04:08:13 -06:00
|
|
|
def currentBan(userId: String): Fu[Option[TempBan]] = coll.find(
|
2016-07-18 02:22:13 -06:00
|
|
|
$doc("_id" -> userId, "b.0" $exists true),
|
2016-04-02 04:13:25 -06:00
|
|
|
$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
|
|
|
|
2017-08-05 02:35:45 -06:00
|
|
|
def hasCurrentBan(userId: String): Fu[Boolean] = currentBan(userId).map(_.isDefined)
|
2017-08-04 08:12:21 -06:00
|
|
|
|
2016-07-18 04:44:54 -06:00
|
|
|
def completionRate(userId: String): Fu[Option[Double]] =
|
|
|
|
coll.primitiveOne[List[Outcome]]($id(userId), "o").map(~_) map { outcomes =>
|
|
|
|
outcomes.collect {
|
2017-04-14 11:42:38 -06:00
|
|
|
case Outcome.RageQuit | Outcome.Sitting | Outcome.NoPlay => false
|
2017-02-14 08:34:07 -07:00
|
|
|
case Outcome.Good => true
|
2016-07-18 04:44:54 -06:00
|
|
|
} match {
|
|
|
|
case c if c.size >= 5 => Some(c.count(identity).toDouble / c.size)
|
2017-02-14 08:34:07 -07:00
|
|
|
case _ => none
|
2016-07-18 04:44:54 -06:00
|
|
|
}
|
2015-04-25 15:06:44 -06:00
|
|
|
}
|
|
|
|
|
2016-07-18 04:44:54 -06:00
|
|
|
def bans(userId: String): Fu[List[TempBan]] =
|
|
|
|
coll.primitiveOne[List[TempBan]]($doc("_id" -> userId, "b.0" $exists true), "b").map(~_)
|
|
|
|
|
2015-04-25 23:44:35 -06:00
|
|
|
def bans(userIds: List[String]): Fu[Map[String, Int]] = coll.find(
|
2016-04-02 04:13:25 -06:00
|
|
|
$inIds(userIds),
|
|
|
|
$doc("b" -> true)
|
|
|
|
).cursor[Bdoc]().gather[List]().map {
|
2015-04-25 23:44:35 -06:00
|
|
|
_.flatMap { obj =>
|
|
|
|
obj.getAs[String]("_id") flatMap { id =>
|
2016-07-18 02:22:13 -06:00
|
|
|
obj.getAs[Barr]("b") map { id -> _.stream.size }
|
2015-04-25 23:44:35 -06:00
|
|
|
}
|
2017-03-26 05:50:31 -06:00
|
|
|
}(scala.collection.breakOut)
|
2015-04-25 23:44:35 -06:00
|
|
|
}
|
|
|
|
|
2015-07-13 09:32:14 -06:00
|
|
|
private def save(outcome: Outcome): String => Funit = userId => {
|
|
|
|
coll.findAndUpdate(
|
2016-07-18 04:44:54 -06:00
|
|
|
selector = $id(userId),
|
2016-04-02 04:13:25 -06:00
|
|
|
update = $doc("$push" -> $doc(
|
|
|
|
"o" -> $doc(
|
2015-07-13 09:32:14 -06:00
|
|
|
"$each" -> List(outcome),
|
2017-02-14 08:34:07 -07:00
|
|
|
"$slice" -> -20
|
|
|
|
)
|
2015-07-13 09:32:14 -06:00
|
|
|
)),
|
|
|
|
fetchNewObject = true,
|
2017-02-14 08:34:07 -07:00
|
|
|
upsert = true
|
|
|
|
).map(_.value)
|
2015-04-25 12:48:13 -06:00
|
|
|
} map2 UserRecordBSONHandler.read flatMap {
|
2017-02-14 08:34:07 -07:00
|
|
|
case None => fufail(s"can't find record for user $userId")
|
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
|
|
|
|
|
|
|
private def legiferate(record: UserRecord): Funit = record.newBan ?? { ban =>
|
|
|
|
coll.update(
|
2016-07-18 02:22:13 -06:00
|
|
|
$id(record.userId),
|
|
|
|
$unset("o") ++
|
|
|
|
$push(
|
2016-04-02 04:13:25 -06:00
|
|
|
"b" -> $doc(
|
2015-04-25 15:06:44 -06:00
|
|
|
"$each" -> List(ban),
|
2017-02-14 08:34:07 -07:00
|
|
|
"$slice" -> -30
|
|
|
|
)
|
2015-04-25 15:06:44 -06:00
|
|
|
)
|
|
|
|
).void
|
|
|
|
}
|
2015-04-25 12:48:13 -06:00
|
|
|
}
|