lila/modules/report/src/main/Report.scala

177 lines
5.0 KiB
Scala

package lila.report
import org.joda.time.DateTime
import cats.data.NonEmptyList
import lila.user.User
case class Report(
_id: Report.ID, // also the url slug
user: User.ID, // the reportee
reason: Reason,
room: Room,
atoms: NonEmptyList[Report.Atom], // most recent first
score: Report.Score,
inquiry: Option[Report.Inquiry],
open: Boolean,
done: Option[Report.Done]
) extends Reason.WithReason {
import Report.{ Atom, Score }
implicit private val ordering = scala.math.Ordering.Double.TotalOrdering
def id = _id
def slug = _id
def closed = !open
def suspect = SuspectId(user)
def add(atom: Atom) =
atomBy(atom.by)
.fold(copy(atoms = atom :: atoms)) { existing =>
if (existing.text contains atom.text) this
else
copy(
atoms = {
existing.copy(
at = atom.at,
score = atom.score,
text = s"${existing.text}\n\n${atom.text}"
) :: atoms.toList.filterNot(_.by == atom.by)
}.toNel | atoms
)
}
.recomputeScore
def recomputeScore =
copy(
score = atoms.toList.foldLeft(Score(0))(_ + _.score)
)
def recentAtom: Atom = atoms.head
def oldestAtom: Atom = atoms.last
def bestAtom: Atom = bestAtoms(1).headOption | recentAtom
def bestAtoms(nb: Int): List[Atom] =
atoms.toList.sortBy { a =>
(-a.score.value, -a.at.getSeconds)
} take nb
def onlyAtom: Option[Atom] = atoms.tail.isEmpty option atoms.head
def atomBy(reporterId: ReporterId): Option[Atom] = atoms.toList.find(_.by == reporterId)
def bestAtomByHuman: Option[Atom] = bestAtoms(10).find(_.byHuman)
def unprocessedCheat = open && isCheat
def unprocessedOther = open && isOther
def unprocessedComm = open && isComm
def process(by: User) =
copy(
open = false,
done = Report.Done(by.id, DateTime.now).some
)
def userIds: List[User.ID] = user :: atoms.toList.map(_.by.value)
def isRecentComm = room == Room.Comm && open
def isRecentCommOf(sus: Suspect) = isRecentComm && user == sus.user.id
def isAppeal = room == Room.Other && atoms.head.text == Report.appealText
def isSpontaneous = room == Room.Other && atoms.head.text == Report.spontaneousText
}
object Report {
type ID = String
case class Score(value: Double) extends AnyVal {
def +(s: Score) = Score(s.value + value)
def color =
if (value >= 150) "red"
else if (value >= 100) "orange"
else if (value >= 50) "yellow"
else "green"
def atLeast(v: Int) = Score(value atLeast v)
}
implicit val scoreIso = lila.common.Iso.double[Score](Score.apply, _.value)
case class Atom(
by: ReporterId,
text: String,
score: Score,
at: DateTime
) {
def simplifiedText = text.linesIterator.filterNot(_ startsWith "[AUTOREPORT]") mkString "\n"
def byHuman = !byLichess && by != ReporterId.irwin
def byLichess = by == ReporterId.lichess
}
case class Done(by: User.ID, at: DateTime)
case class Inquiry(mod: User.ID, seenAt: DateTime)
case class WithSuspect(report: Report, suspect: Suspect, isOnline: Boolean) {
def urgency: Int =
report.score.value.toInt +
(isOnline ?? 1000) +
(report.closed ?? -999999)
}
case class ByAndAbout(by: List[Report], about: List[Report]) {
def userIds = by.flatMap(_.userIds) ::: about.flatMap(_.userIds)
}
case class Candidate(
reporter: Reporter,
suspect: Suspect,
reason: Reason,
text: String
) extends Reason.WithReason {
def scored(score: Score) = Candidate.Scored(this, score)
def isAutomatic = reporter.id == ReporterId.lichess
def isAutoComm = isAutomatic && isComm
def isAutoBoost = isAutomatic && isBoost
def isIrwinCheat = reporter.id == ReporterId.irwin && isCheat
def isCoachReview = isOther && text.contains("COACH REVIEW")
}
object Candidate {
case class Scored(candidate: Candidate, score: Score) {
def withScore(f: Score => Score) = copy(score = f(score))
def atom =
Atom(
by = candidate.reporter.id,
text = candidate.text,
score = score,
at = DateTime.now
)
}
}
private[report] val spontaneousText = "Spontaneous inquiry"
private[report] val appealText = "Appeal"
def make(c: Candidate.Scored, existing: Option[Report]) =
c match {
case c @ Candidate.Scored(candidate, score) =>
existing.fold(
Report(
_id = lila.common.ThreadLocalRandom nextString 8,
user = candidate.suspect.user.id,
reason = candidate.reason,
room = Room(candidate.reason),
atoms = NonEmptyList.one(c.atom),
score = score,
inquiry = none,
open = true,
done = none
)
)(_ add c.atom)
}
private[report] case class SnoozeKey(snoozerId: User.ID, reportId: Report.ID) extends lila.memo.Snooze.Key
}