2013-05-06 08:51:19 -06:00
|
|
|
package controllers
|
|
|
|
|
2020-12-14 07:57:49 -07:00
|
|
|
import ornicar.scalalib.Zero
|
|
|
|
import play.api.data._
|
|
|
|
import play.api.data.Forms._
|
|
|
|
import play.api.mvc._
|
2020-04-29 08:58:36 -06:00
|
|
|
import scala.annotation.nowarn
|
2020-12-14 07:57:49 -07:00
|
|
|
import views._
|
2019-12-21 14:22:26 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
import lila.api.{ BodyContext, Context }
|
2013-05-06 08:51:19 -06:00
|
|
|
import lila.app._
|
2017-09-04 11:41:58 -06:00
|
|
|
import lila.chat.Chat
|
2019-12-13 07:30:20 -07:00
|
|
|
import lila.common.{ EmailAddress, HTTPRequest, IpAddress }
|
2019-07-22 04:48:52 -06:00
|
|
|
import lila.mod.UserSearch
|
2019-12-04 18:47:46 -07:00
|
|
|
import lila.report.{ Suspect, Mod => AsMod }
|
2021-08-06 03:35:19 -06:00
|
|
|
import lila.security.{ FingerHash, Granter, Permission }
|
2021-03-09 04:18:55 -07:00
|
|
|
import lila.user.{ User => UserModel, Title, Holder }
|
2013-05-06 08:51:19 -06:00
|
|
|
|
2019-12-04 18:47:46 -07:00
|
|
|
final class Mod(
|
|
|
|
env: Env,
|
2019-12-05 14:51:18 -07:00
|
|
|
reportC: => Report,
|
|
|
|
userC: => User
|
2019-12-04 18:47:46 -07:00
|
|
|
) extends LilaController(env) {
|
2013-05-06 08:51:19 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
private def modApi = env.mod.api
|
2019-12-04 16:39:16 -07:00
|
|
|
private def modLogApi = env.mod.logApi
|
|
|
|
private def assessApi = env.mod.assessApi
|
2013-05-06 08:51:19 -06:00
|
|
|
|
2021-03-09 04:18:55 -07:00
|
|
|
implicit private def asMod(holder: Holder) = AsMod(holder.user)
|
|
|
|
|
2019-12-31 11:17:47 -07:00
|
|
|
def alt(username: String, v: Boolean) =
|
|
|
|
OAuthModBody(_.CloseAccount) { me =>
|
|
|
|
withSuspect(username) { sus =>
|
|
|
|
for {
|
|
|
|
inquiry <- env.report.api.inquiries ofModId me.id
|
2021-03-09 04:18:55 -07:00
|
|
|
_ <- modApi.setAlt(me, sus, v)
|
2021-04-07 13:21:01 -06:00
|
|
|
_ <- (v && sus.user.enabled) ?? env.closeAccount(sus.user, me)
|
2019-12-31 11:17:47 -07:00
|
|
|
} yield (inquiry, sus).some
|
|
|
|
}
|
|
|
|
}(ctx =>
|
2020-09-21 01:28:28 -06:00
|
|
|
me => { case (inquiry, suspect) =>
|
|
|
|
reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
|
2019-12-31 11:17:47 -07:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def engine(username: String, v: Boolean) =
|
|
|
|
OAuthModBody(_.MarkEngine) { me =>
|
|
|
|
withSuspect(username) { sus =>
|
|
|
|
for {
|
|
|
|
inquiry <- env.report.api.inquiries ofModId me.id
|
2021-03-09 04:18:55 -07:00
|
|
|
_ <- modApi.setEngine(me, sus, v)
|
2019-12-13 07:30:20 -07:00
|
|
|
} yield (inquiry, sus).some
|
|
|
|
}
|
|
|
|
}(ctx =>
|
2020-09-21 01:28:28 -06:00
|
|
|
me => { case (inquiry, suspect) =>
|
|
|
|
reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
)
|
2013-05-10 03:56:34 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def publicChat =
|
2021-05-11 08:07:37 -06:00
|
|
|
Secure(_.PublicChatView) { implicit ctx => _ =>
|
2021-09-23 09:19:53 -06:00
|
|
|
env.mod.publicChat.all map { case (tournamentsAndChats, swissesAndChats) =>
|
|
|
|
Ok(html.mod.publicChat(tournamentsAndChats, swissesAndChats))
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2016-09-04 07:34:35 -06:00
|
|
|
|
2021-03-11 04:02:40 -07:00
|
|
|
def publicChatTimeout =
|
|
|
|
SecureBody(_.ChatTimeout) { implicit ctx => me =>
|
|
|
|
FormResult(lila.chat.ChatTimeout.form) { data =>
|
|
|
|
env.chat.api.userChat.publicTimeout(data, me)
|
|
|
|
}(ctx.body)
|
|
|
|
}
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def booster(username: String, v: Boolean) =
|
|
|
|
OAuthModBody(_.MarkBooster) { me =>
|
|
|
|
withSuspect(username) { prev =>
|
|
|
|
for {
|
|
|
|
inquiry <- env.report.api.inquiries ofModId me.id
|
2021-03-09 04:18:55 -07:00
|
|
|
suspect <- modApi.setBoost(me, prev, v)
|
2019-12-13 07:30:20 -07:00
|
|
|
} yield (inquiry, suspect).some
|
|
|
|
}
|
|
|
|
}(ctx =>
|
2020-09-21 01:28:28 -06:00
|
|
|
me => { case (inquiry, suspect) =>
|
|
|
|
reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
)
|
2016-10-17 04:01:00 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def troll(username: String, v: Boolean) =
|
|
|
|
OAuthModBody(_.Shadowban) { me =>
|
2017-10-28 13:50:20 -06:00
|
|
|
withSuspect(username) { prev =>
|
2017-09-11 15:14:36 -06:00
|
|
|
for {
|
2019-12-04 16:39:16 -07:00
|
|
|
inquiry <- env.report.api.inquiries ofModId me.id
|
2021-03-09 04:18:55 -07:00
|
|
|
suspect <- modApi.setTroll(me, prev, v)
|
2018-08-20 14:59:56 -06:00
|
|
|
} yield (inquiry, suspect).some
|
2017-03-30 10:21:52 -06:00
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
}(ctx =>
|
2020-09-21 01:28:28 -06:00
|
|
|
me => { case (inquiry, suspect) =>
|
|
|
|
reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
)
|
2017-03-30 10:21:52 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def warn(username: String, subject: String) =
|
|
|
|
OAuthModBody(_.ModMessage) { me =>
|
2020-08-04 03:57:06 -06:00
|
|
|
env.mod.presets.pmPresets.get().named(subject) ?? { preset =>
|
2019-12-13 07:30:20 -07:00
|
|
|
withSuspect(username) { prev =>
|
|
|
|
for {
|
|
|
|
inquiry <- env.report.api.inquiries ofModId me.id
|
2021-03-09 04:18:55 -07:00
|
|
|
suspect <- modApi.setTroll(me, prev, prev.user.marks.troll)
|
2020-08-04 03:57:06 -06:00
|
|
|
_ <- env.msg.api.systemPost(suspect.user.id, preset.text)
|
2020-01-26 22:07:27 -07:00
|
|
|
_ <- env.mod.logApi.modMessage(me.id, suspect.user.id, preset.name)
|
2019-12-13 07:30:20 -07:00
|
|
|
} yield (inquiry, suspect).some
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}(ctx =>
|
2020-09-21 01:28:28 -06:00
|
|
|
me => { case (inquiry, suspect) =>
|
|
|
|
reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
)
|
2013-05-10 03:56:34 -06:00
|
|
|
|
2020-12-19 09:01:02 -07:00
|
|
|
def kid(username: String) =
|
2020-12-21 01:20:07 -07:00
|
|
|
OAuthMod(_.SetKidMode) { _ => me =>
|
2020-12-19 09:01:02 -07:00
|
|
|
modApi.setKid(me.id, username) map some
|
|
|
|
}(actionResult(username))
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def deletePmsAndChats(username: String) =
|
|
|
|
OAuthMod(_.Shadowban) { _ => _ =>
|
|
|
|
withSuspect(username) { sus =>
|
2021-03-31 12:27:34 -06:00
|
|
|
env.mod.publicChat.deleteAll(sus) >>
|
2020-01-27 16:04:22 -07:00
|
|
|
env.msg.api.deleteAllBy(sus.user) map some
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
}(actionResult(username))
|
2017-10-28 14:29:25 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def disableTwoFactor(username: String) =
|
2021-04-07 12:20:15 -06:00
|
|
|
OAuthMod(_.DisableTwoFactor) { _ => me =>
|
|
|
|
modApi.disableTwoFactor(me.id, username) map some
|
|
|
|
}(actionResult(username))
|
2018-05-06 04:03:28 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def closeAccount(username: String) =
|
|
|
|
OAuthMod(_.CloseAccount) { _ => me =>
|
|
|
|
env.user.repo named username flatMap {
|
|
|
|
_ ?? { user =>
|
2021-04-07 13:21:01 -06:00
|
|
|
env.closeAccount(user, me) map some
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2018-08-20 14:59:56 -06:00
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
}(actionResult(username))
|
2013-05-06 08:51:19 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def reopenAccount(username: String) =
|
2020-03-03 11:54:10 -07:00
|
|
|
OAuthMod(_.CloseAccount) { _ => me =>
|
2019-12-13 07:30:20 -07:00
|
|
|
modApi.reopenAccount(me.id, username) map some
|
|
|
|
}(actionResult(username))
|
2014-02-01 06:13:22 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def reportban(username: String, v: Boolean) =
|
|
|
|
OAuthMod(_.ReportBan) { _ => me =>
|
|
|
|
withSuspect(username) { sus =>
|
2021-03-09 04:18:55 -07:00
|
|
|
modApi.setReportban(me, sus, v) map some
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
}(actionResult(username))
|
2017-07-05 16:15:46 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def rankban(username: String, v: Boolean) =
|
|
|
|
OAuthMod(_.RemoveRanking) { _ => me =>
|
|
|
|
withSuspect(username) { sus =>
|
2021-03-09 04:18:55 -07:00
|
|
|
modApi.setRankban(me, sus, v) map some
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
}(actionResult(username))
|
2018-01-30 10:27:32 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def impersonate(username: String) =
|
|
|
|
Auth { implicit ctx => me =>
|
|
|
|
if (username == "-" && env.mod.impersonate.isImpersonated(me)) fuccess {
|
|
|
|
env.mod.impersonate.stop(me)
|
|
|
|
Redirect(routes.User.show(me.username))
|
|
|
|
}
|
2020-10-17 11:24:56 -06:00
|
|
|
else if (isGranted(_.Impersonate) || (isGranted(_.Admin) && username.toLowerCase == "lichess"))
|
2020-05-05 22:11:15 -06:00
|
|
|
OptionFuRedirect(env.user.repo named username) { user =>
|
|
|
|
env.mod.impersonate.start(me, user)
|
|
|
|
fuccess(routes.User.show(user.username))
|
|
|
|
}
|
|
|
|
else notFound
|
|
|
|
}
|
2017-08-03 04:43:29 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def setTitle(username: String) =
|
|
|
|
SecureBody(_.SetTitle) { implicit ctx => me =>
|
|
|
|
implicit def req = ctx.body
|
2020-08-21 14:40:37 -06:00
|
|
|
lila.user.UserForm.title
|
2020-07-10 02:26:46 -06:00
|
|
|
.bindFromRequest()
|
|
|
|
.fold(
|
|
|
|
_ => fuccess(redirect(username, mod = true)),
|
|
|
|
title =>
|
|
|
|
modApi.setTitle(me.id, username, title map Title.apply) >>
|
2021-05-27 11:49:48 -06:00
|
|
|
env.mailer.automaticEmail.onTitleSet(username) >>-
|
2020-07-10 02:26:46 -06:00
|
|
|
env.user.lightUserApi.invalidate(UserModel normalize username) inject
|
|
|
|
redirect(username, mod = false)
|
|
|
|
)
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2013-09-11 04:38:16 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def setEmail(username: String) =
|
|
|
|
SecureBody(_.SetEmail) { implicit ctx => me =>
|
|
|
|
implicit def req = ctx.body
|
|
|
|
OptionFuResult(env.user.repo named username) { user =>
|
|
|
|
env.security.forms
|
|
|
|
.modEmail(user)
|
2020-07-07 02:34:48 -06:00
|
|
|
.bindFromRequest()
|
2020-05-05 22:11:15 -06:00
|
|
|
.fold(
|
|
|
|
err => BadRequest(err.toString).fuccess,
|
|
|
|
rawEmail => {
|
|
|
|
val email = env.security.emailAddressValidator
|
2020-08-16 06:48:46 -06:00
|
|
|
.validate(EmailAddress(rawEmail)) err s"Invalid email $rawEmail"
|
2020-05-05 22:11:15 -06:00
|
|
|
modApi.setEmail(me.id, user.id, email.acceptable) inject redirect(user.username, mod = true)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2014-02-26 17:18:09 -07:00
|
|
|
|
2021-07-08 12:49:21 -06:00
|
|
|
def inquiryToZulip =
|
|
|
|
Secure(_.SendToZulip) { _ => me =>
|
|
|
|
env.report.api.inquiries ofModId me.id flatMap {
|
|
|
|
case None => Redirect(routes.Report.list).fuccess
|
|
|
|
case Some(report) =>
|
|
|
|
env.user.lightUserApi asyncFallback report.user flatMap { user =>
|
|
|
|
import lila.report.Room
|
|
|
|
import lila.irc.IrcApi.ModDomain
|
|
|
|
env.irc.api.inquiry(
|
|
|
|
user = user,
|
|
|
|
mod = me,
|
|
|
|
domain = report.room match {
|
|
|
|
case Room.Cheat | Room.Boost => ModDomain.Hunt
|
|
|
|
case Room.Comm => ModDomain.Comm
|
2021-08-06 03:35:19 -06:00
|
|
|
// spontaneous inquiry
|
|
|
|
case _ if Granter(_.Admin)(me.user) => ModDomain.Admin
|
|
|
|
case _ if Granter(_.Hunter)(me.user) => ModDomain.Hunt // heuristic
|
|
|
|
case _ if Granter(_.Shusher)(me.user) => ModDomain.Comm
|
|
|
|
case _ => ModDomain.Admin
|
|
|
|
|
2021-07-14 00:20:12 -06:00
|
|
|
},
|
2021-08-06 03:35:19 -06:00
|
|
|
room = if (report.isSpontaneous) "Spontaneous inquiry" else report.room.name
|
2021-07-08 12:49:21 -06:00
|
|
|
) inject NoContent
|
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2021-07-08 12:49:21 -06:00
|
|
|
}
|
2015-08-12 05:17:16 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def table =
|
|
|
|
Secure(_.ModLog) { implicit ctx => _ =>
|
|
|
|
modApi.allMods map { html.mod.table(_) }
|
|
|
|
}
|
2020-03-03 12:34:33 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
private def communications(username: String, priv: Boolean) =
|
|
|
|
Secure { perms =>
|
|
|
|
if (priv) perms.ViewPrivateComms else perms.Shadowban
|
|
|
|
} { implicit ctx => me =>
|
|
|
|
OptionFuOk(env.user.repo named username) { user =>
|
2021-03-10 12:51:47 -07:00
|
|
|
implicit val renderIp = env.mod.ipRender(me)
|
2019-12-20 09:50:00 -07:00
|
|
|
env.game.gameRepo
|
|
|
|
.recentPovsByUserFromSecondary(user, 80)
|
|
|
|
.mon(_.mod.comm.segment("recentPovs"))
|
|
|
|
.flatMap { povs =>
|
2019-12-13 07:30:20 -07:00
|
|
|
priv.?? {
|
2019-12-20 09:50:00 -07:00
|
|
|
env.chat.api.playerChat
|
|
|
|
.optionsByOrderedIds(povs.map(_.gameId).map(Chat.Id.apply))
|
|
|
|
.mon(_.mod.comm.segment("playerChats"))
|
2019-12-13 07:30:20 -07:00
|
|
|
} zip
|
2019-12-20 09:50:00 -07:00
|
|
|
priv.?? {
|
2020-01-27 16:04:22 -07:00
|
|
|
env.msg.api
|
|
|
|
.recentByForMod(user, 30)
|
2019-12-20 09:50:00 -07:00
|
|
|
.mon(_.mod.comm.segment("pms"))
|
|
|
|
} zip
|
|
|
|
(env.shutup.api getPublicLines user.id)
|
|
|
|
.mon(_.mod.comm.segment("publicChats")) zip
|
|
|
|
env.user.noteApi
|
2021-07-08 12:49:21 -06:00
|
|
|
.byUserForMod(user.id)
|
2019-12-20 09:50:00 -07:00
|
|
|
.mon(_.mod.comm.segment("notes")) zip
|
|
|
|
env.mod.logApi
|
|
|
|
.userHistory(user.id)
|
|
|
|
.mon(_.mod.comm.segment("history")) zip
|
|
|
|
env.report.api.inquiries
|
|
|
|
.ofModId(me.id)
|
2021-02-10 05:13:45 -07:00
|
|
|
.mon(_.mod.comm.segment("inquiries")) zip
|
|
|
|
env.security.userLogins(user, 100).flatMap {
|
|
|
|
userC.loginsTableData(user, _, 100)
|
2021-04-19 03:04:15 -06:00
|
|
|
} flatMap { case ((((((chats, convos), publicLines), notes), history), inquiry), logins) =>
|
2021-02-10 05:13:45 -07:00
|
|
|
if (priv) {
|
|
|
|
if (!inquiry.??(_.isRecentCommOf(Suspect(user)))) {
|
2021-06-22 14:15:30 -06:00
|
|
|
env.irc.api.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value))
|
2021-02-10 05:13:45 -07:00
|
|
|
if (isGranted(_.MonitoredMod))
|
2021-06-22 14:15:30 -06:00
|
|
|
env.irc.api.monitorMod(
|
2021-02-10 05:13:45 -07:00
|
|
|
me.id,
|
|
|
|
"eyes",
|
2021-03-30 07:36:11 -06:00
|
|
|
s"spontaneously checked out @${user.username}'s private comms",
|
2021-07-08 12:49:21 -06:00
|
|
|
lila.irc.IrcApi.ModDomain.Comm
|
2021-02-10 05:13:45 -07:00
|
|
|
)
|
2020-09-21 01:28:28 -06:00
|
|
|
}
|
2021-02-10 05:13:45 -07:00
|
|
|
}
|
2021-04-19 03:04:15 -06:00
|
|
|
env.appeal.api.byUserIds(user.id :: logins.userLogins.otherUserIds) map { appeals =>
|
|
|
|
html.mod.communication(
|
|
|
|
me,
|
|
|
|
user,
|
|
|
|
(povs zip chats) collect {
|
|
|
|
case (p, Some(c)) if c.nonEmpty => p -> c
|
|
|
|
} take 15,
|
|
|
|
convos,
|
|
|
|
publicLines,
|
|
|
|
notes.filter(_.from != "irwin"),
|
|
|
|
history,
|
|
|
|
logins,
|
|
|
|
appeals,
|
|
|
|
priv
|
|
|
|
)
|
|
|
|
}
|
2020-09-21 01:28:28 -06:00
|
|
|
}
|
2014-05-22 13:01:54 -06:00
|
|
|
}
|
2017-03-30 04:18:00 -06:00
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2014-05-22 13:01:54 -06:00
|
|
|
|
2020-08-16 06:42:29 -06:00
|
|
|
def communicationPublic(username: String) = communications(username, priv = false)
|
|
|
|
def communicationPrivate(username: String) = communications(username, priv = true)
|
2017-09-04 11:41:58 -06:00
|
|
|
|
2017-05-02 08:40:37 -06:00
|
|
|
protected[controllers] def redirect(username: String, mod: Boolean = true) =
|
2019-08-02 03:33:37 -06:00
|
|
|
Redirect(userUrl(username, mod))
|
|
|
|
|
|
|
|
protected[controllers] def userUrl(username: String, mod: Boolean = true) =
|
|
|
|
s"${routes.User.show(username).url}${mod ?? "?mod"}"
|
2015-01-13 03:44:12 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def refreshUserAssess(username: String) =
|
|
|
|
Secure(_.MarkEngine) { implicit ctx => me =>
|
|
|
|
OptionFuResult(env.user.repo named username) { user =>
|
2021-02-19 04:40:30 -07:00
|
|
|
assessApi.refreshAssessOf(user) >>
|
2021-03-09 04:18:55 -07:00
|
|
|
env.irwin.api.requests.fromMod(Suspect(user), me) >>
|
2020-05-05 22:11:15 -06:00
|
|
|
userC.renderModZoneActions(username)
|
|
|
|
}
|
2018-08-22 08:55:07 -06:00
|
|
|
}
|
2016-01-10 21:09:37 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def spontaneousInquiry(username: String) =
|
|
|
|
Secure(_.SeeReport) { implicit ctx => me =>
|
|
|
|
OptionFuResult(env.user.repo named username) { user =>
|
2021-07-04 04:16:30 -06:00
|
|
|
(isGranted(_.Appeals) ?? env.appeal.api.exists(user)) flatMap { isAppeal =>
|
2021-07-07 11:31:18 -06:00
|
|
|
isAppeal.??(env.report.api.inquiries.ongoingAppealOf(user.id)) flatMap {
|
|
|
|
case Some(ongoing) if ongoing.mod != me.id =>
|
|
|
|
env.user.lightUserApi.asyncFallback(ongoing.mod) map { mod =>
|
|
|
|
Redirect(routes.Appeal.show(user.username))
|
|
|
|
.flashFailure(s"Currently processed by ${mod.name}")
|
|
|
|
}
|
|
|
|
case _ =>
|
|
|
|
val f =
|
|
|
|
if (isAppeal) env.report.api.inquiries.appeal _
|
|
|
|
else env.report.api.inquiries.spontaneous _
|
|
|
|
f(me, Suspect(user)) inject {
|
|
|
|
if (isAppeal) Redirect(s"${routes.Appeal.show(user.username)}#appeal-actions")
|
|
|
|
else redirect(user.username, mod = true)
|
|
|
|
}
|
2020-07-31 05:06:12 -06:00
|
|
|
}
|
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2017-05-10 17:07:04 -06:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def gamify =
|
2021-05-11 08:07:37 -06:00
|
|
|
Secure(_.GamifyView) { implicit ctx => _ =>
|
2020-05-05 22:11:15 -06:00
|
|
|
env.mod.gamify.leaderboards zip
|
2020-09-21 01:28:28 -06:00
|
|
|
env.mod.gamify.history(orCompute = true) map { case (leaderboards, history) =>
|
|
|
|
Ok(html.mod.gamify.index(leaderboards, history))
|
|
|
|
}
|
2020-01-14 08:38:45 -07:00
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
def gamifyPeriod(periodStr: String) =
|
2021-05-11 08:07:37 -06:00
|
|
|
Secure(_.GamifyView) { implicit ctx => _ =>
|
2020-05-05 22:11:15 -06:00
|
|
|
lila.mod.Gamify.Period(periodStr).fold(notFound) { period =>
|
|
|
|
env.mod.gamify.leaderboards map { leaderboards =>
|
|
|
|
Ok(html.mod.gamify.period(leaderboards, period))
|
|
|
|
}
|
2020-01-14 08:38:45 -07:00
|
|
|
}
|
|
|
|
}
|
2016-01-11 20:16:20 -07:00
|
|
|
|
2021-06-07 09:08:47 -06:00
|
|
|
def activity = activityOf("team", "month")
|
|
|
|
|
|
|
|
def activityOf(who: String, period: String) =
|
|
|
|
Secure(_.GamifyView) { implicit ctx => me =>
|
|
|
|
env.mod.activity(who, period)(me.user) map { activity =>
|
|
|
|
Ok(html.mod.activity(activity))
|
2021-06-07 04:13:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 09:35:55 -06:00
|
|
|
def queues(period: String) =
|
|
|
|
Secure(_.GamifyView) { implicit ctx => me =>
|
|
|
|
env.mod.queueStats(period) map { stats =>
|
|
|
|
Ok(html.mod.queueStats(stats))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def search =
|
2021-04-11 02:36:52 -06:00
|
|
|
SecureBody(_.UserSearch) { implicit ctx => me =>
|
2020-05-05 22:11:15 -06:00
|
|
|
implicit def req = ctx.body
|
|
|
|
val f = UserSearch.form
|
2020-07-10 02:26:46 -06:00
|
|
|
f.bindFromRequest()
|
|
|
|
.fold(
|
2021-04-11 02:36:52 -06:00
|
|
|
err => BadRequest(html.mod.search(me, err, Nil)).fuccess,
|
|
|
|
query => env.mod.search(query) map { html.mod.search(me, f.fill(query), _) }
|
2020-07-10 02:26:46 -06:00
|
|
|
)
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2016-06-10 18:13:57 -06:00
|
|
|
|
2021-04-11 02:36:52 -06:00
|
|
|
protected[controllers] def searchTerm(me: Holder, q: String)(implicit ctx: Context) = {
|
2019-03-20 21:21:47 -06:00
|
|
|
val query = UserSearch exact q
|
2019-12-13 07:30:20 -07:00
|
|
|
env.mod.search(query) map { users =>
|
2021-04-11 02:36:52 -06:00
|
|
|
Ok(html.mod.search(me, UserSearch.form fill query, users))
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2019-03-20 21:21:47 -06:00
|
|
|
}
|
2019-02-12 23:58:56 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def print(fh: String) =
|
2021-04-11 02:36:52 -06:00
|
|
|
SecureBody(_.ViewPrintNoIP) { implicit ctx => me =>
|
2020-05-05 22:11:15 -06:00
|
|
|
val hash = FingerHash(fh)
|
|
|
|
for {
|
|
|
|
uids <- env.security.api recentUserIdsByFingerHash hash
|
|
|
|
users <- env.user.repo usersFromSecondary uids.reverse
|
|
|
|
withEmails <- env.user.repo withEmailsU users
|
|
|
|
uas <- env.security.api.printUas(hash)
|
2021-04-11 02:36:52 -06:00
|
|
|
} yield Ok(html.mod.search.print(me, hash, withEmails, uas, env.security.printBan blocks hash))
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2019-08-09 03:23:31 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def printBan(v: Boolean, fh: String) =
|
|
|
|
Secure(_.PrintBan) { _ => _ =>
|
|
|
|
env.security.printBan.toggle(FingerHash(fh), v) inject
|
|
|
|
Redirect(routes.Mod.print(fh))
|
|
|
|
}
|
2020-03-07 04:03:57 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def singleIp(ip: String) =
|
2021-03-10 12:51:47 -07:00
|
|
|
SecureBody(_.ViewPrintNoIP) { implicit ctx => me =>
|
|
|
|
implicit val renderIp = env.mod.ipRender(me)
|
|
|
|
env.mod.ipRender.decrypt(ip) ?? { address =>
|
2021-02-19 09:09:25 -07:00
|
|
|
for {
|
|
|
|
uids <- env.security.api recentUserIdsByIp address
|
|
|
|
users <- env.user.repo usersFromSecondary uids.reverse
|
|
|
|
withEmails <- env.user.repo withEmailsU users
|
|
|
|
uas <- env.security.api.ipUas(address)
|
2021-04-11 02:36:52 -06:00
|
|
|
} yield Ok(html.mod.search.ip(me, address, withEmails, uas, env.security.firewall blocksIp address))
|
2021-02-19 09:09:25 -07:00
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2020-03-07 04:03:57 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def singleIpBan(v: Boolean, ip: String) =
|
2020-07-10 02:26:46 -06:00
|
|
|
Secure(_.IpBan) { ctx => _ =>
|
2020-06-03 21:11:50 -06:00
|
|
|
val op =
|
|
|
|
if (v) env.security.firewall.blockIps _
|
|
|
|
else env.security.firewall.unblockIps _
|
2021-02-19 09:09:25 -07:00
|
|
|
op(IpAddress from ip) inject {
|
2020-07-10 02:26:46 -06:00
|
|
|
if (HTTPRequest isXhr ctx.req) jsonOkResult
|
|
|
|
else Redirect(routes.Mod.singleIp(ip))
|
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2016-06-20 10:44:30 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def chatUser(username: String) =
|
|
|
|
Secure(_.ChatTimeout) { _ => _ =>
|
|
|
|
implicit val lightUser = env.user.lightUserSync
|
|
|
|
JsonOptionOk {
|
|
|
|
env.chat.api.userChat userModInfo username map2 lila.chat.JsonView.userModInfo
|
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2016-06-20 10:44:30 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def permissions(username: String) =
|
|
|
|
Secure(_.ChangePermission) { implicit ctx => me =>
|
|
|
|
OptionOk(env.user.repo named username) { user =>
|
|
|
|
html.mod.permissions(user, me)
|
|
|
|
}
|
2016-10-17 04:01:00 -06:00
|
|
|
}
|
2017-06-23 04:49:07 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def savePermissions(username: String) =
|
|
|
|
SecureBody(_.ChangePermission) { implicit ctx => me =>
|
|
|
|
implicit def req = ctx.body
|
|
|
|
import lila.security.Permission
|
|
|
|
OptionFuResult(env.user.repo named username) { user =>
|
|
|
|
Form(
|
|
|
|
single("permissions" -> list(text.verifying(Permission.allByDbKey.contains _)))
|
2020-07-10 02:26:46 -06:00
|
|
|
).bindFromRequest()
|
|
|
|
.fold(
|
|
|
|
_ => BadRequest(html.mod.permissions(user, me)).fuccess,
|
|
|
|
permissions => {
|
|
|
|
val newPermissions = Permission(permissions) diff Permission(user.roles)
|
2021-03-09 04:18:55 -07:00
|
|
|
modApi.setPermissions(me, user.username, Permission(permissions)) >> {
|
2021-05-27 11:49:48 -06:00
|
|
|
newPermissions(Permission.Coach) ?? env.mailer.automaticEmail.onBecomeCoach(user)
|
2020-07-10 02:26:46 -06:00
|
|
|
} >> {
|
2021-06-05 03:30:31 -06:00
|
|
|
Permission(permissions)
|
2021-06-05 15:50:28 -06:00
|
|
|
.exists(_ is Permission.SeeReport) ?? env.plan.api.setLifetime(user)
|
2020-07-10 02:26:46 -06:00
|
|
|
} inject Redirect(routes.Mod.permissions(username)).flashSuccess
|
|
|
|
}
|
|
|
|
)
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2017-08-15 07:50:47 -06:00
|
|
|
}
|
2017-09-11 15:14:36 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def emailConfirm =
|
|
|
|
SecureBody(_.SetEmail) { implicit ctx => me =>
|
|
|
|
get("q") match {
|
|
|
|
case None => Ok(html.mod.emailConfirm("", none, none)).fuccess
|
|
|
|
case Some(rawQuery) =>
|
|
|
|
val query = rawQuery.trim.split(' ').toList
|
|
|
|
val email = query.headOption
|
|
|
|
.map(EmailAddress.apply) flatMap env.security.emailAddressValidator.validate
|
|
|
|
val username = query lift 1
|
|
|
|
def tryWith(setEmail: EmailAddress, q: String): Fu[Option[Result]] =
|
|
|
|
env.mod.search(UserSearch.exact(q)) flatMap {
|
|
|
|
case List(UserModel.WithEmails(user, _)) =>
|
|
|
|
(!user.everLoggedIn).?? {
|
|
|
|
lila.mon.user.register.modConfirmEmail.increment()
|
|
|
|
modApi.setEmail(me.id, user.id, setEmail)
|
|
|
|
} >>
|
|
|
|
env.user.repo.email(user.id) map { email =>
|
2020-09-21 01:28:28 -06:00
|
|
|
Ok(html.mod.emailConfirm("", user.some, email)).some
|
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
case _ => fuccess(none)
|
|
|
|
}
|
|
|
|
email.?? { em =>
|
|
|
|
tryWith(em.acceptable, em.acceptable.value) orElse {
|
|
|
|
username ?? { tryWith(em.acceptable, _) }
|
2021-05-15 01:44:10 -06:00
|
|
|
} recover lila.db.recoverDuplicateKey(_ => none)
|
2020-05-05 22:11:15 -06:00
|
|
|
} getOrElse BadRequest(html.mod.emailConfirm(rawQuery, none, none)).fuccess
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def chatPanic =
|
|
|
|
Secure(_.Shadowban) { implicit ctx => _ =>
|
|
|
|
Ok(html.mod.chatPanic(env.chat.panic.get)).fuccess
|
|
|
|
}
|
2017-10-28 15:40:52 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def chatPanicPost =
|
|
|
|
OAuthMod(_.Shadowban) { req => me =>
|
|
|
|
val v = getBool("v", req)
|
|
|
|
env.chat.panic.set(v)
|
2021-06-22 14:15:30 -06:00
|
|
|
env.irc.api.chatPanic(me, v)
|
2019-12-13 07:30:20 -07:00
|
|
|
fuccess(().some)
|
2021-02-08 07:25:21 -07:00
|
|
|
}(_ => _ => _ => Redirect(routes.Mod.chatPanic).fuccess)
|
2017-10-28 15:40:52 -06:00
|
|
|
|
2020-08-04 03:11:42 -06:00
|
|
|
def presets(group: String) =
|
|
|
|
Secure(_.Presets) { implicit ctx => _ =>
|
|
|
|
env.mod.presets.get(group).fold(notFound) { setting =>
|
|
|
|
Ok(html.mod.presets(group, setting, setting.form)).fuccess
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def presetsUpdate(group: String) =
|
|
|
|
SecureBody(_.Presets) { implicit ctx => _ =>
|
|
|
|
implicit val req = ctx.body
|
|
|
|
env.mod.presets.get(group).fold(notFound) { setting =>
|
|
|
|
setting.form
|
|
|
|
.bindFromRequest()
|
|
|
|
.fold(
|
|
|
|
err => BadRequest(html.mod.presets(group, setting, err)).fuccess,
|
|
|
|
v => setting.setString(v.toString) inject Redirect(routes.Mod.presets(group)).flashSuccess
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def eventStream =
|
2020-12-14 07:57:49 -07:00
|
|
|
Scoped() { req => me =>
|
|
|
|
IfGranted(_.Admin, req, me) {
|
|
|
|
noProxyBuffer(Ok.chunked(env.mod.stream())).fuccess
|
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2018-07-29 23:01:25 -06:00
|
|
|
|
2018-08-20 14:59:56 -06:00
|
|
|
private def withSuspect[A](username: String)(f: Suspect => Fu[A])(implicit zero: Zero[A]): Fu[A] =
|
2019-12-04 16:39:16 -07:00
|
|
|
env.report.api getSuspect username flatMap {
|
2018-08-20 14:59:56 -06:00
|
|
|
_ ?? f
|
2017-09-11 15:14:36 -06:00
|
|
|
}
|
2018-08-20 14:59:56 -06:00
|
|
|
|
2021-03-09 04:18:55 -07:00
|
|
|
private def OAuthMod[A](perm: Permission.Selector)(f: RequestHeader => Holder => Fu[Option[A]])(
|
|
|
|
secure: Context => Holder => A => Fu[Result]
|
2020-05-05 22:11:15 -06:00
|
|
|
): Action[Unit] =
|
|
|
|
SecureOrScoped(perm)(
|
|
|
|
secure = ctx => me => f(ctx.req)(me) flatMap { _ ?? secure(ctx)(me) },
|
|
|
|
scoped = req =>
|
|
|
|
me =>
|
|
|
|
f(req)(me) flatMap { res =>
|
|
|
|
res.isDefined ?? fuccess(jsonOkResult)
|
|
|
|
}
|
|
|
|
)
|
2021-03-09 04:18:55 -07:00
|
|
|
private def OAuthModBody[A](perm: Permission.Selector)(f: Holder => Fu[Option[A]])(
|
|
|
|
secure: BodyContext[_] => Holder => A => Fu[Result]
|
2020-05-05 22:11:15 -06:00
|
|
|
): Action[AnyContent] =
|
|
|
|
SecureOrScopedBody(perm)(
|
|
|
|
secure = ctx => me => f(me) flatMap { _ ?? secure(ctx)(me) },
|
|
|
|
scoped = _ =>
|
|
|
|
me =>
|
|
|
|
f(me) flatMap { res =>
|
|
|
|
res.isDefined ?? fuccess(jsonOkResult)
|
|
|
|
}
|
|
|
|
)
|
2018-08-20 14:59:56 -06:00
|
|
|
|
2020-04-29 08:58:36 -06:00
|
|
|
private def actionResult(
|
|
|
|
username: String
|
2021-03-09 04:18:55 -07:00
|
|
|
)(ctx: Context)(@nowarn("cat=unused") user: Holder)(@nowarn("cat=unused") res: Any) =
|
2019-12-04 18:47:46 -07:00
|
|
|
if (HTTPRequest isSynchronousHttp ctx.req) fuccess(redirect(username))
|
|
|
|
else userC.renderModZoneActions(username)(ctx)
|
2013-05-06 08:51:19 -06:00
|
|
|
}
|