286 lines
9.3 KiB
Scala
286 lines
9.3 KiB
Scala
package views.html.mod
|
|
|
|
import cats.data.NonEmptyList
|
|
import controllers.routes
|
|
import scala.util.matching.Regex
|
|
|
|
import lila.api.Context
|
|
import lila.app.templating.Environment._
|
|
import lila.app.ui.ScalatagsTemplate._
|
|
import lila.common.String.html.richText
|
|
import lila.report.Reason
|
|
import lila.report.Report
|
|
import lila.user.User
|
|
|
|
object inquiry {
|
|
|
|
// simul game study relay tournament
|
|
private val commFlagRegex = """\[FLAG\] (\w+)/(\w{8})(?:/w)? (.+)""".r
|
|
|
|
private def renderAtomText(text: String, highlight: Boolean) =
|
|
text.split("\n").map { line =>
|
|
val (link, text) = line match {
|
|
case commFlagRegex(tpe, id, text) =>
|
|
val path = tpe match {
|
|
case "game" => routes.Round.watcher(id, "white").url
|
|
case "relay" => routes.RelayRound.show("-", "-", id).url
|
|
case "tournament" => routes.Tournament.show(id).url
|
|
case "swiss" => routes.Swiss.show(id).url
|
|
case _ => s"/$tpe/$id"
|
|
}
|
|
a(href := path)(path).some -> text
|
|
case text => None -> text
|
|
}
|
|
frag(
|
|
link,
|
|
" ",
|
|
if (highlight) communication.highlightBad(text) else frag(text),
|
|
" "
|
|
)
|
|
}
|
|
|
|
def apply(in: lila.mod.Inquiry)(implicit ctx: Context) = {
|
|
def renderReport(r: Report) =
|
|
div(cls := "doc report")(
|
|
r.bestAtoms(10).map { atom =>
|
|
div(cls := "atom")(
|
|
h3(
|
|
reportScore(atom.score),
|
|
userIdLink(atom.by.value.some, withOnline = false),
|
|
" for ",
|
|
strong(r.reason.name),
|
|
" ",
|
|
momentFromNow(atom.at)
|
|
),
|
|
p(renderAtomText(atom.simplifiedText, r.isComm))
|
|
)
|
|
}
|
|
)
|
|
|
|
def renderNote(r: lila.user.Note)(implicit ctx: Context) =
|
|
(!r.dox || isGranted(_.Admin)) option div(cls := "doc note")(
|
|
h3("by ", userIdLink(r.from.some, withOnline = false), ", ", momentFromNow(r.date)),
|
|
p(richText(r.text, expandImg = false))
|
|
)
|
|
|
|
def autoNextInput = input(cls := "auto-next", tpe := "hidden", name := "next", value := "1")
|
|
|
|
def markButton(active: Boolean) =
|
|
submitButton(
|
|
cls := List(
|
|
"fbt icon" -> true,
|
|
"active" -> active
|
|
)
|
|
)
|
|
|
|
div(id := "inquiry")(
|
|
i(title := "Costello the Inquiry Octopus", cls := "costello"),
|
|
div(cls := "meat")(
|
|
userLink(in.user, withBestRating = true, params = "?mod"),
|
|
div(cls := "docs reports")(
|
|
div(cls := "expendable")(
|
|
in.allReports.map(renderReport)
|
|
)
|
|
),
|
|
isGranted(_.ModLog) option div(
|
|
cls := List(
|
|
"dropper counter history" -> true,
|
|
"empty" -> in.history.isEmpty
|
|
)
|
|
)(
|
|
span(
|
|
countTag(in.history.size),
|
|
"Mod log"
|
|
),
|
|
in.history.nonEmpty option div(
|
|
ul(
|
|
in.history.map { e =>
|
|
li(
|
|
userIdLink(e.mod.some, withOnline = false),
|
|
" ",
|
|
b(e.showAction),
|
|
" ",
|
|
e.details,
|
|
" ",
|
|
momentFromNow(e.date)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
),
|
|
div(
|
|
cls := List(
|
|
"dropper counter notes" -> true,
|
|
"empty" -> in.notes.isEmpty
|
|
)
|
|
)(
|
|
span(
|
|
countTag(in.notes.size),
|
|
"Notes"
|
|
),
|
|
div(
|
|
postForm(cls := "note", action := s"${routes.User.writeNote(in.user.username)}?note")(
|
|
textarea(name := "text", placeholder := "Write a mod note"),
|
|
input(tpe := "hidden", name := "mod", value := "true"),
|
|
div(cls := "submission")(
|
|
submitButton(cls := "button thin")("SEND")
|
|
)
|
|
),
|
|
in.notes.map(renderNote)
|
|
)
|
|
)
|
|
),
|
|
div(cls := "links")(
|
|
boostOpponents(in.report) map { opponents =>
|
|
a(href := s"${routes.GameMod.index(in.user.id)}?opponents=${opponents.toList mkString ", "}")(
|
|
"View",
|
|
br,
|
|
"Games"
|
|
)
|
|
},
|
|
isGranted(_.Shadowban) option
|
|
a(href := routes.Mod.communicationPublic(in.user.id))("View", br, "Comms")
|
|
),
|
|
div(cls := "actions")(
|
|
isGranted(_.ModMessage) option div(cls := "dropper warn buttons")(
|
|
iconTag(""),
|
|
div(
|
|
env.mod.presets.pmPresets.get().value.map { preset =>
|
|
postForm(action := routes.Mod.warn(in.user.username, preset.name))(
|
|
submitButton(cls := "fbt", title := preset.text)(preset.name),
|
|
autoNextInput
|
|
)
|
|
}
|
|
)
|
|
),
|
|
isGranted(_.MarkEngine) option {
|
|
val url = routes.Mod.engine(in.user.username, !in.user.marks.engine).url
|
|
div(cls := "dropper engine buttons")(
|
|
postForm(action := url, title := "Mark as cheat")(
|
|
markButton(in.user.marks.engine)(dataIcon := ""),
|
|
autoNextInput
|
|
),
|
|
thenForms(url, markButton(false))
|
|
)
|
|
},
|
|
isGranted(_.MarkBooster) option {
|
|
val url = routes.Mod.booster(in.user.username, !in.user.marks.boost).url
|
|
div(cls := "dropper booster buttons")(
|
|
postForm(action := url, cls := "main", title := "Mark as booster or sandbagger")(
|
|
markButton(in.user.marks.boost)(dataIcon := ""),
|
|
autoNextInput
|
|
),
|
|
thenForms(url, markButton(false))
|
|
)
|
|
},
|
|
isGranted(_.Shadowban) option {
|
|
val url = routes.Mod.troll(in.user.username, !in.user.marks.troll).url
|
|
div(cls := "dropper shadowban buttons")(
|
|
postForm(
|
|
action := url,
|
|
title := (if (in.user.marks.troll) "Un-shadowban" else "Shadowban"),
|
|
cls := "main"
|
|
)(
|
|
markButton(in.user.marks.troll)(dataIcon := ""),
|
|
autoNextInput
|
|
),
|
|
thenForms(url, markButton(false))
|
|
)
|
|
},
|
|
isGranted(_.CloseAccount) option {
|
|
val url = routes.Mod.alt(in.user.username, !in.user.marks.alt).url
|
|
div(cls := "dropper alt buttons")(
|
|
postForm(action := url, cls := "main", title := "Close alt account")(
|
|
markButton(in.user.marks.alt)(i("A")),
|
|
autoNextInput
|
|
),
|
|
thenForms(url, markButton(false))
|
|
)
|
|
},
|
|
div(cls := "dropper more buttons")(
|
|
iconTag(""),
|
|
div(
|
|
isGranted(_.NotifySlack) option {
|
|
postForm(action := routes.Mod.notifySlack(in.user.id))(
|
|
submitButton(cls := "fbt")("Notify Slack")
|
|
)
|
|
},
|
|
postForm(action := routes.Report.xfiles(in.report.id))(
|
|
submitButton(cls := List("fbt" -> true, "active" -> (in.report.room.key == "xfiles")))(
|
|
"Move to X-Files"
|
|
),
|
|
autoNextInput
|
|
),
|
|
div(cls := "separator"),
|
|
lila.memo.Snooze.Duration.all.map { snooze =>
|
|
postForm(action := snoozeUrl(in.report, snooze.toString))(
|
|
submitButton(cls := "fbt")(s"Snooze ${snooze.name}"),
|
|
autoNextInput
|
|
)
|
|
}
|
|
)
|
|
)
|
|
),
|
|
div(cls := "actions close")(
|
|
span(cls := "switcher", title := "Automatically open next report")(
|
|
span(cls := "switch")(
|
|
form3.cmnToggle("auto-next", "auto-next", checked = true)
|
|
)
|
|
),
|
|
postForm(
|
|
action := routes.Report.process(in.report.id),
|
|
title := "Dismiss this report as processed. (Hotkey: d)",
|
|
cls := "process"
|
|
)(
|
|
submitButton(dataIcon := "", cls := "fbt"),
|
|
autoNextInput
|
|
),
|
|
postForm(
|
|
action := routes.Report.inquiry(in.report.id),
|
|
title := "Cancel the inquiry, re-instore the report",
|
|
cls := "cancel"
|
|
)(
|
|
submitButton(dataIcon := "", cls := "fbt")
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
private def snoozeUrl(report: Report, duration: String): String =
|
|
if (report.isAppeal) routes.Appeal.snooze(report.user, duration).url
|
|
else routes.Report.snooze(report.id, duration).url
|
|
|
|
private def boostOpponents(report: Report): Option[NonEmptyList[User.ID]] =
|
|
(report.reason == Reason.Boost) ?? {
|
|
report.atoms.toList
|
|
.withFilter(_.byLichess)
|
|
.flatMap(_.text.linesIterator)
|
|
.collect {
|
|
case farmWithRegex(userId) => List(userId)
|
|
case sandbagWithRegex(userIds) => userIds.split(' ').toList.map(_.trim.replace("@", ""))
|
|
}
|
|
.flatten
|
|
.distinct
|
|
.toNel
|
|
}
|
|
|
|
private val farmWithRegex =
|
|
("^Boosting: farms rating points from @(" + User.historicalUsernameRegex.pattern + ")").r.unanchored
|
|
private val sandbagWithRegex =
|
|
"^Sandbagging: throws games to (.+)".r.unanchored
|
|
|
|
private def thenForms(url: String, button: Tag) =
|
|
div(
|
|
postForm(
|
|
action := url,
|
|
button("And stay on this report"),
|
|
form3.hidden("next", "0")
|
|
),
|
|
postForm(
|
|
action := url,
|
|
button("Then open profile"),
|
|
form3.hidden("then", "profile")
|
|
)
|
|
)
|
|
}
|