Revert "Revert "redesign Irwin API""

This reverts commit 1df1e559b0.
pull/4013/head
Thibault Duplessis 2018-02-03 09:30:41 -05:00
parent 4fda166190
commit 14ef3298b4
17 changed files with 92 additions and 180 deletions

View File

@ -28,15 +28,6 @@ object Irwin extends LilaController {
}
}
def getRequest = Open { implicit ctx =>
ModExternalBot {
Env.irwin.api.requests.getAndStart map {
case None => NotFound
case Some(req) => Ok(req.id)
}
}
}
def assessment(username: String) = Open { implicit ctx =>
ModExternalBot {
OptionFuResult(UserRepo named username) { user =>

View File

@ -4,7 +4,7 @@ import lila.api.Context
import lila.app._
import lila.chat.Chat
import lila.common.{ IpAddress, EmailAddress }
import lila.report.{ Suspect, Mod => AsMod }
import lila.report.{ Suspect, Mod => AsMod, SuspectId }
import lila.user.{ UserRepo, User => UserModel }
import views._
@ -194,7 +194,7 @@ object Mod extends LilaController {
def refreshUserAssess(username: String) = Secure(_.MarkEngine) { implicit ctx => me =>
assessApi.refreshAssessByUsername(username) >>
Env.irwin.api.requests.fromMod(lila.user.User normalize username, me) inject
Env.irwin.api.requests.fromMod(SuspectId normalize username, me) inject
redirect(username)
}

View File

@ -248,7 +248,7 @@ object User extends LilaController {
Env.plan.api.recentChargesOf(user) zip
Env.report.api.byAndAbout(user, 20) zip
Env.pref.api.getPref(user) zip
Env.irwin.api.status(user) flatMap {
Env.irwin.api.reports.withPovs(user) flatMap {
case emails ~ spy ~ assess ~ history ~ charges ~ reports ~ pref ~ irwin =>
val familyUserIds = user.id :: spy.otherUserIds.toList
Env.playban.api.bans(familyUserIds) zip

View File

@ -28,37 +28,7 @@ moreCss = cssTag("mod-irwin.css")) {
</h1>
<div class="both">
<div class="queue">
<table class="slist">
<thead>
<tr>
<th>Queued request</th>
<th>Created</th>
<th>Started</th>
</tr>
</thead>
<tbody>
@dashboard.queue.map { req =>
<tr class="request @if(req.isInProgress){started}">
<td>
@userIdLink(req.id.some, params = "?mod")
</td>
<td class="little for">
@momentFromNow(req.createdAt)<br />
@req.notifyUserId.map { modId =>
by @userIdSpanMini(modId, withOnline = true)
}.getOrElse {
by @req.origin
}
</td>
<td class="little">
@req.startedAt.map { at =>
@momentFromNow(at)
}
</td>
</tr>
}
</tbody>
</table>
We no longer know what irwin is up to.
</div>
<div class="recent">
<table class="slist">
@ -73,7 +43,7 @@ moreCss = cssTag("mod-irwin.css")) {
@dashboard.recent.map { rep =>
<tr class="report">
<td>
@userIdLink(rep.id.some, params = "?mod")
@userIdLink(rep.suspectId.value.some, params = "?mod")
</td>
<td class="little completed">
@momentFromNow(rep.date)

View File

@ -1,4 +1,4 @@
@(report: lila.irwin.IrwinReport.WithPovs, request: Option[lila.irwin.IrwinRequest])(implicit ctx: Context)
@(report: lila.irwin.IrwinReport.WithPovs)(implicit ctx: Context)
@percentClass(percent: Int) = { @percent match {
case p if p < 30 => {green}
@ -16,12 +16,6 @@ case _ => {red}
</a>
<div class="infos">
<p>Updated @momentFromNow(report.report.date)</p>
@request.map { r =>
<p>New update is in progress</p>
<p>Requested @momentFromNow(r.createdAt) by: @r.origin</p>
}.getOrElse {
<p>Hit "Evaluate" to update</p>
}
</div>
<div class="assess text">
<strong class="@percentClass(report.report.activation)">@report.report.activation%</strong>

View File

@ -1,4 +1,4 @@
@(u: User, emails: User.Emails, spy: lila.security.UserSpy, optionAggregateAssessment: Option[lila.evaluation.PlayerAggregateAssessment.WithGames], bans: Map[String, Int], history: List[lila.mod.Modlog], charges: List[lila.plan.Charge], reports: lila.report.Report.ByAndAbout, pref: lila.pref.Pref, irwinStatus: lila.irwin.IrwinStatus, notes: List[lila.user.Note])(implicit ctx: Context)
@(u: User, emails: User.Emails, spy: lila.security.UserSpy, optionAggregateAssessment: Option[lila.evaluation.PlayerAggregateAssessment.WithGames], bans: Map[String, Int], history: List[lila.mod.Modlog], charges: List[lila.plan.Charge], reports: lila.report.Report.ByAndAbout, pref: lila.pref.Pref, irwinReport: Option[lila.irwin.IrwinReport.WithPovs], notes: List[lila.user.Note])(implicit ctx: Context)
@import lila.evaluation.Display
@import lila.pref.Pref
@ -105,13 +105,8 @@
<strong class="text inline" data-icon="%">Notable preferences:</strong>
@if(pref.keyboardMove != Pref.KeyboardMove.NO) { [keyboard moves] } else { none }
</div>
@irwinStatus.report.map { report =>
@views.html.irwin.irwinReport(report, irwinStatus.request)
}.getOrElse {
@irwinStatus.request.map { request =>
<strong class="inline text" data-icon="">Irwin request:</strong>
in progress, requested @momentFromNow(request.createdAt) by: @request.origin
}
@irwinReport.map { report =>
@views.html.irwin.irwinReport(report)
}
@optionAggregateAssessment.map { pag =>
<div class="evaluation results">

View File

@ -405,7 +405,6 @@ POST /mod/chat-panic controllers.Mod.chatPanicPost
GET /irwin controllers.Irwin.dashboard
GET /irwin/stream controllers.Irwin.eventStream
POST /irwin/report controllers.Irwin.saveReport
GET /irwin/request controllers.Irwin.getRequest
GET /irwin/:username/assessment controllers.Irwin.assessment(username: String)
GET /irwin/users-mark-and-current-report controllers.Irwin.usersMarkAndCurrentReport(ids: String)

View File

@ -1,8 +1,10 @@
package lila.irwin
import reactivemongo.bson._
import lila.db.dsl._
import lila.db.BSON
import reactivemongo.bson._
import lila.report.ReporterId
object BSONHandlers {
@ -35,19 +37,6 @@ object BSONHandlers {
private implicit val GameReportBSONHandler = Macros.handler[GameReport]
private implicit val PvBSONHandler = nullableHandler[Int, BSONInteger]
private implicit val ReporterIdBSONHandler = stringIsoHandler[ReporterId](ReporterId.reporterIdIso)
implicit val ReportBSONHandler = Macros.handler[IrwinReport]
private implicit val RequestOriginBSONHandler: BSONHandler[BSONString, IrwinRequest.Origin] =
new BSONHandler[BSONString, IrwinRequest.Origin] {
import IrwinRequest.Origin, Origin._
def read(bs: BSONString) = bs.value match {
case "moderator" => Moderator
case "report" => Report
case "tournament" => Tournament
case "leaderboard" => Leaderboard
case _ => sys error s"Invalid origin ${bs.value}"
}
def write(x: Origin) = BSONString(x.key)
}
implicit val RequestBSONHandler = Macros.handler[IrwinRequest]
}

View File

@ -20,7 +20,6 @@ final class Env(
) {
private val reportColl = db(config getString "collection.report")
private val requestColl = db(config getString "collection.request")
lazy val irwinModeSetting = settingStore[String](
"irwinMode",
@ -28,31 +27,23 @@ final class Env(
text = "Allow Irwin to: [mark|report|none]".some
)
val api = new IrwinApi(
val stream = new IrwinStream(system)
lazy val api = new IrwinApi(
reportColl = reportColl,
requestColl = requestColl,
modApi = modApi,
reportApi = reportApi,
notifyApi = notifyApi,
bus = system.lilaBus,
mode = irwinModeSetting.get
)
lazy val stream = new IrwinStream(system)
scheduler.future(5 minutes, "irwin tournament leaders") {
tournamentApi.allCurrentLeadersInStandard flatMap api.requests.fromTournamentLeaders
}
scheduler.future(15 minutes, "irwin leaderboards") {
userCache.getTop50Online flatMap api.requests.fromLeaderboard
}
system.lilaBus.subscribe(system.actorOf(Props(new Actor {
import lila.hub.actorApi.report._
def receive = {
case Created(userId, "cheat" | "cheatprint", reporterId) => api.requests.insert(userId, _.Report, none)
case Processed(userId, "cheat" | "cheatprint") => api.requests.drop(userId)
}
})), 'report)
}
object Env {

View File

@ -5,38 +5,31 @@ import reactivemongo.bson._
import lila.db.dsl._
import lila.game.{ Pov, GameRepo }
import lila.report.{ Report, Mod, Suspect, Reporter }
import lila.report.{ Report, Mod, Suspect, Reporter, SuspectId, ModId }
import lila.tournament.{ Tournament, TournamentTop }
import lila.user.{ User, UserRepo }
final class IrwinApi(
reportColl: Coll,
requestColl: Coll,
modApi: lila.mod.ModApi,
reportApi: lila.report.ReportApi,
notifyApi: lila.notify.NotifyApi,
bus: lila.common.Bus,
mode: () => String
) {
import BSONHandlers._
def status(user: User): Fu[IrwinStatus] =
reports.withPovs(user) zip requests.get(user.id) map { (IrwinStatus.apply _).tupled }
def dashboard: Fu[IrwinDashboard] = for {
queue <- requestColl.find($empty).sort($sort asc "priority").list[IrwinRequest](20)
recent <- reportColl.find($empty).sort($sort desc "date").list[IrwinReport](20)
} yield IrwinDashboard(queue, recent)
def dashboard: Fu[IrwinDashboard] =
reportColl.find($empty).sort($sort desc "date").list[IrwinReport](20) map IrwinDashboard.apply
object reports {
def insert(report: IrwinReport) = (mode() != "none") ?? {
for {
_ <- reportColl.update($id(report.id), report, upsert = true)
request <- requests get report.id
_ <- request.??(r => requests.drop(r.id))
_ <- request.??(notifyRequester)
_ <- reportColl.update($id(report._id), report, upsert = true)
_ <- markOrReport(report)
_ <- notification(report)
} yield ()
}
@ -59,10 +52,10 @@ final class IrwinApi(
private def markOrReport(report: IrwinReport): Funit =
if (report.activation > 90 && mode() == "mark")
modApi.autoMark(report.userId, "irwin") >>-
modApi.autoMark(report.suspectId, ModId.irwin) >>-
lila.mon.mod.irwin.mark()
else if (report.activation >= 60 && mode() != "none") for {
suspect <- getSuspect(report.userId)
suspect <- getSuspect(report.suspectId.value)
irwin <- UserRepo byId "irwin" flatten s"Irwin user not found" map Mod.apply
_ <- reportApi.create(Report.Candidate(
reporter = Reporter(irwin.user),
@ -78,32 +71,17 @@ final class IrwinApi(
import IrwinRequest.Origin
def getAndStart: Fu[Option[IrwinRequest]] =
requestColl
.find($doc("startedAt" $exists false))
.sort($sort asc "priority")
.uno[IrwinRequest] flatMap {
_ ?? { request =>
requestColl.updateField($id(request.id), "startedAt", DateTime.now) inject request.some
}
}
def fromMod(suspectId: SuspectId, mod: User) = {
notification.add(suspectId, ModId(mod.id))
insert(suspectId, _.Moderator)
}
def get(reportedId: User.ID): Fu[Option[IrwinRequest]] =
requestColl.byId[IrwinRequest](reportedId)
def fromMod(reportedId: User.ID, mod: User) = insert(reportedId, _.Moderator, mod.id.some)
private[irwin] def drop(reportedId: User.ID): Funit = requestColl.remove($id(reportedId)).void
private[irwin] def insert(reportedId: User.ID, origin: Origin.type => Origin, notifyUserId: Option[User.ID]) = {
val request = IrwinRequest.make(reportedId, origin(Origin), notifyUserId)
get(reportedId) flatMap {
case Some(prev) if prev.isInProgress => funit
case Some(prev) if prev.priority isAfter request.priority =>
requestColl.update($id(request.id), request).void
case Some(prev) => funit
case None => requestColl.insert(request).void
}
private[irwin] def insert(suspectId: SuspectId, origin: Origin.type => Origin): Funit = fuccess {
bus.publish(IrwinRequest(
suspect = suspectId,
origin = origin(Origin),
date = DateTime.now
), 'irwin)
}
private[irwin] def fromTournamentLeaders(leaders: Map[Tournament, TournamentTop]): Funit =
@ -111,20 +89,29 @@ final class IrwinApi(
case (tour, top) =>
val userIds = top.value.zipWithIndex.filter(_._2 <= tour.nbPlayers * 2 / 100).map(_._1.userId)
lila.common.Future.applySequentially(userIds) { userId =>
insert(userId, _.Tournament, none)
insert(SuspectId(userId), _.Tournament)
}
}
private[irwin] def fromLeaderboard(leaders: List[User]): Funit =
lila.common.Future.applySequentially(leaders) { user =>
insert(user.id, _.Leaderboard, none)
insert(SuspectId(user.id), _.Leaderboard)
}
}
private def notifyRequester(request: IrwinRequest): Funit = request.notifyUserId ?? { userId =>
import lila.notify.{ Notification, IrwinDone }
notifyApi.addNotification(
Notification.make(Notification.Notifies(userId), IrwinDone(request.id))
)
object notification {
private var subs = Map.empty[SuspectId, ModId]
def add(suspectId: SuspectId, modId: ModId): Unit = subs += (suspectId -> modId)
private[IrwinApi] def apply(report: IrwinReport): Funit =
subs.get(report.suspectId) ?? { modId =>
subs = subs - report.suspectId
import lila.notify.{ Notification, IrwinDone }
notifyApi.addNotification(
Notification.make(Notification.Notifies(modId.value), IrwinDone(report.suspectId.value))
)
}
}
}

View File

@ -2,10 +2,7 @@ package lila.irwin
import org.joda.time.DateTime
case class IrwinDashboard(
queue: List[IrwinRequest],
recent: List[IrwinReport]
) {
case class IrwinDashboard(recent: List[IrwinReport]) {
def lastSeenAt = recent.headOption.map(_.date)

View File

@ -1,9 +1,10 @@
package lila.irwin
import lila.game.{ Game, Pov }
import org.joda.time.DateTime
import lila.game.{ Game, Pov }
import lila.report.{ SuspectId, ReporterId }
case class IrwinReport(
_id: String, // user id
activation: Int, // 0 = clean, 100 = cheater
@ -11,12 +12,9 @@ case class IrwinReport(
date: DateTime
) {
def id = _id
def userId = _id
def suspectId = SuspectId(_id)
}
case class IrwinStatus(report: Option[IrwinReport.WithPovs], request: Option[IrwinRequest])
object IrwinReport {
case class GameReport(

View File

@ -2,21 +2,14 @@ package lila.irwin
import org.joda.time.DateTime
import lila.report.{ SuspectId, ReporterId }
import lila.user.User
case class IrwinRequest(
_id: User.ID,
suspect: SuspectId,
origin: IrwinRequest.Origin,
priority: DateTime, // older = more prioritary; affected by origin
createdAt: DateTime,
startedAt: Option[DateTime],
notifyUserId: Option[User.ID]
) {
def id = _id
def isInProgress = startedAt.isDefined
}
date: DateTime
)
object IrwinRequest {
@ -26,24 +19,7 @@ object IrwinRequest {
object Origin {
case object Moderator extends Origin
case object Report extends Origin
case object Tournament extends Origin
case object Leaderboard extends Origin
}
def make(userId: User.ID, origin: Origin, notifyUserId: Option[User.ID]) = IrwinRequest(
_id = userId,
origin = origin,
priority = DateTime.now minusHours originPriorityDays(origin),
createdAt = DateTime.now,
startedAt = none,
notifyUserId = notifyUserId
)
private def originPriorityDays(origin: Origin) = origin match {
case Origin.Moderator => 1000
case Origin.Report => 20
case Origin.Tournament => -1000
case Origin.Leaderboard => -1000
}
}

View File

@ -17,11 +17,25 @@ final class IrwinStream(system: ActorSystem) {
onStart = channel => {
val actor = system.actorOf(Props(new Actor {
def receive = {
case request: IrwinRequest =>
channel push Json.obj(
"t" -> "request",
"origin" -> request.origin.key,
"user" -> request.suspect.value
)
case lila.hub.actorApi.report.Created(userId, "cheat" | "cheatprint", _) =>
channel push Json.obj(
"t" -> "report",
"t" -> "reportCreated",
"user" -> userId
)
case lila.hub.actorApi.report.Processed(userId, "cheat" | "cheatprint") =>
lila.user.UserRepo.isEngine(userId) foreach { marked =>
channel push Json.obj(
"t" -> "reportProcessed",
"user" -> userId,
"marked" -> marked
)
}
case lila.hub.actorApi.mod.MarkCheater(userId, value) =>
channel push Json.obj(
"t" -> "mark",
@ -30,7 +44,7 @@ final class IrwinStream(system: ActorSystem) {
)
}
}))
system.lilaBus.subscribe(actor, 'report, 'adjustCheater)
system.lilaBus.subscribe(actor, 'report, 'adjustCheater, 'irwin)
},
onComplete = {
stream.foreach { actor =>

View File

@ -8,6 +8,7 @@ import lila.evaluation.Statistics
import lila.evaluation.{ AccountAction, Analysed, PlayerAssessment, PlayerAggregateAssessment, PlayerFlags, PlayerAssessments, Assessible }
import lila.game.{ Game, Player, GameRepo, Source, Pov }
import lila.user.{ User, UserRepo }
import lila.report.{ SuspectId, ModId }
import reactivemongo.api.ReadPreference
import reactivemongo.bson._
@ -112,7 +113,7 @@ final class AssessApi(
case Some(playerAggregateAssessment) => playerAggregateAssessment.action match {
case AccountAction.Engine | AccountAction.EngineAndBan =>
UserRepo.getTitle(userId).flatMap {
case None => modApi.autoMark(userId, "lichess")
case None => modApi.autoMark(SuspectId(userId), ModId.lichess)
case Some(title) => fuccess {
val reason = s"Would mark as engine, but has a $title title"
reporter ! lila.hub.actorApi.report.Cheater(userId, playerAggregateAssessment.reportText(reason, 3))

View File

@ -1,7 +1,7 @@
package lila.mod
import lila.common.{ IpAddress, EmailAddress }
import lila.report.{ Mod, Suspect, Room }
import lila.report.{ Mod, ModId, Suspect, SuspectId, Room }
import lila.security.Permission
import lila.security.{ Firewall, UserSpy, Store => SecurityStore }
import lila.user.{ User, UserRepo, LightUserApi }
@ -33,10 +33,10 @@ final class ModApi(
}
}
def autoMark(username: String, modId: User.ID): Funit = for {
sus <- reportApi.getSuspect(username) flatten s"No such suspect $username"
def autoMark(suspectId: SuspectId, modId: ModId): Funit = for {
sus <- reportApi.getSuspect(suspectId.value) flatten s"No such suspect $suspectId"
unengined <- logApi.wasUnengined(sus)
_ <- if (unengined) funit else reportApi.getMod(modId) flatMap {
_ <- if (unengined) funit else reportApi.getMod(modId.value) flatMap {
_ ?? { mod =>
lila.mon.cheat.autoMark.count()
setEngine(mod, sus, true)

View File

@ -4,10 +4,20 @@ import lila.user.User
case class Mod(user: User) extends AnyVal
case class ModId(value: User.ID) extends AnyVal
object ModId {
def lichess = ModId("lichess")
def irwin = ModId("irwin")
def normalize(username: String) = ModId(User normalize username)
}
case class Suspect(user: User) extends AnyVal {
def set(f: User => User) = copy(user = f(user))
}
case class SuspectId(value: User.ID) extends AnyVal
object SuspectId {
def normalize(username: String) = SuspectId(User normalize username)
}
case class Victim(user: User) extends AnyVal