package lila.irwin import org.joda.time.DateTime import reactivemongo.api.bson._ import reactivemongo.api.ReadPreference import lila.analyse.Analysis import lila.analyse.AnalysisRepo import lila.common.Bus import lila.db.dsl._ import lila.game.{ Game, GameRepo, Pov, Query } import lila.report.{ Mod, ModId, Report, Reporter, Suspect, SuspectId } import lila.tournament.{ Tournament, TournamentTop } import lila.user.{ Holder, User, UserRepo } final class IrwinApi( reportColl: Coll, gameRepo: GameRepo, userRepo: UserRepo, analysisRepo: AnalysisRepo, modApi: lila.mod.ModApi, reportApi: lila.report.ReportApi, notifyApi: lila.notify.NotifyApi, thresholds: lila.memo.SettingStore[IrwinThresholds] )(implicit ec: scala.concurrent.ExecutionContext) { import BSONHandlers._ def dashboard: Fu[IrwinDashboard] = reportColl .find($empty) .sort($sort desc "date") .cursor[IrwinReport]() .list(20) dmap IrwinDashboard.apply object reports { def insert(report: IrwinReport) = reportColl.update.one($id(report._id), report, upsert = true) >> markOrReport(report) >> notification(report) >>- lila.mon.mod.irwin.ownerReport(report.owner).increment().unit def get(user: User): Fu[Option[IrwinReport]] = reportColl.find($id(user.id)).one[IrwinReport] def withPovs(user: User): Fu[Option[IrwinReport.WithPovs]] = get(user) flatMap { _ ?? { report => gameRepo.gamesFromSecondary(report.games.map(_.gameId)) dmap { games => val povs = games.flatMap { g => Pov(g, user) map { g.id -> _ } }.toMap IrwinReport.WithPovs(report, povs).some } } } private def getSuspect(suspectId: User.ID) = userRepo byId suspectId orFail s"suspect $suspectId not found" dmap Suspect.apply private def markOrReport(report: IrwinReport): Funit = userRepo.getTitle(report.suspectId.value) flatMap { title => if (report.activation >= thresholds.get().mark && title.isEmpty) modApi.autoMark(report.suspectId, ModId.irwin, report.note) >>- lila.mon.mod.irwin.mark.increment().unit else if (report.activation >= thresholds.get().report) for { suspect <- getSuspect(report.suspectId.value) irwin <- userRepo.irwin orFail s"Irwin user not found" dmap Mod.apply _ <- reportApi.create( Report.Candidate( reporter = Reporter(irwin.user), suspect = suspect, reason = lila.report.Reason.Cheat, text = s"${report.activation}% over ${report.games.size} games" ) ) } yield lila.mon.mod.irwin.report.increment().unit else funit } } object requests { import IrwinRequest.Origin def fromMod(suspect: Suspect, mod: Holder) = { notification.add(suspect.id, ModId(mod.id)) insert(suspect, _.Moderator) } private[irwin] def insert(suspect: Suspect, origin: Origin.type => Origin): Funit = for { analyzed <- getAnalyzedGames(suspect, 15) more <- getMoreGames(suspect, 20 - analyzed.size) all = analyzed.map { case (game, analysis) => game -> analysis.some } ::: more.map(_ -> none) } yield Bus.publish( IrwinRequest( suspect = suspect, origin = origin(Origin), games = all ), "irwin" ) private[irwin] def fromTournamentLeaders(leaders: Map[Tournament, TournamentTop]): Funit = lila.common.Future.applySequentially(leaders.toList) { case (tour, top) => userRepo byIds top.value.zipWithIndex .filter(_._2 <= tour.nbPlayers * 2 / 100) .map(_._1.userId) .take(20) flatMap { users => lila.common.Future.applySequentially(users) { user => insert(Suspect(user), _.Tournament) } } } private[irwin] def fromLeaderboard(leaders: List[User]): Funit = lila.common.Future.applySequentially(leaders) { user => insert(Suspect(user), _.Leaderboard) } import lila.game.BSONHandlers._ private def baseQuery(suspect: Suspect) = Query.finished ++ Query.variantStandard ++ Query.rated ++ Query.user(suspect.id.value) ++ Query.turnsGt(20) ++ Query.createdSince(DateTime.now minusMonths 6) private def getAnalyzedGames(suspect: Suspect, nb: Int): Fu[List[(Game, Analysis)]] = gameRepo.coll .find(baseQuery(suspect) ++ Query.analysed(true)) .sort(Query.sortCreated) .cursor[Game](ReadPreference.secondaryPreferred) .list(nb) .flatMap(analysisRepo.associateToGames) private def getMoreGames(suspect: Suspect, nb: Int): Fu[List[Game]] = (nb > 0) ?? gameRepo.coll .find(baseQuery(suspect) ++ Query.analysed(false)) .sort(Query.sortCreated) .cursor[Game](ReadPreference.secondaryPreferred) .list(nb) } object notification { private var subs = Map.empty[SuspectId, Set[ModId]] def add(suspectId: SuspectId, modId: ModId): Unit = subs = subs.updated(suspectId, ~subs.get(suspectId) + modId) private[IrwinApi] def apply(report: IrwinReport): Funit = subs.get(report.suspectId) ?? { modIds => subs = subs - report.suspectId import lila.notify.{ IrwinDone, Notification } modIds .map { modId => notifyApi.addNotification( Notification.make(Notification.Notifies(modId.value), IrwinDone(report.suspectId.value)) ) } .sequenceFu .void } } }