230 lines
8.8 KiB
Scala
230 lines
8.8 KiB
Scala
package lila.irc
|
|
|
|
import org.joda.time.DateTime
|
|
|
|
import lila.common.IpAddress
|
|
import lila.common.{ ApiVersion, EmailAddress, Heapsort, IpAddress, LightUser }
|
|
import lila.hub.actorApi.irc._
|
|
import lila.user.Holder
|
|
import lila.user.User
|
|
|
|
final class IrcApi(
|
|
zulip: ZulipClient,
|
|
noteApi: lila.user.NoteApi,
|
|
implicit val lightUser: LightUser.Getter
|
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
|
|
|
import IrcApi._
|
|
|
|
def commReportBurst(user: User): Funit = {
|
|
val md = markdown.linkifyUsers(s"Burst of comm reports about @${user.username}")
|
|
zulip(_.mod.commsPrivate, "burst")(md)
|
|
}
|
|
|
|
def inquiry(user: User, mod: Holder, domain: ModDomain, room: String): Funit = {
|
|
val stream = domain match {
|
|
case ModDomain.Comm => ZulipClient.stream.mod.commsPrivate
|
|
case ModDomain.Hunt => ZulipClient.stream.mod.hunterCheat
|
|
case _ => ZulipClient.stream.mod.adminGeneral
|
|
}
|
|
noteApi
|
|
.byUserForMod(user.id)
|
|
.map(_.headOption.filter(_.date isAfter DateTime.now.minusMinutes(5)))
|
|
.flatMap {
|
|
case None =>
|
|
zulip.sendAndGetLink(stream, "/" + user.username)(
|
|
s"${markdown.userLink(mod.user.username)} :monkahmm: is looking at a $room report about **${markdown
|
|
.userLink(user.username)}**"
|
|
)
|
|
case Some(note) =>
|
|
zulip.sendAndGetLink(stream, "/" + user.username)(
|
|
s"${markdown.modLink(mod.user)} :pepenote: **${markdown
|
|
.userLink(user.username)}** (${markdown.userNotesLink(user.username)}):\n" +
|
|
markdown.linkifyUsers(note.text take 2000)
|
|
)
|
|
}
|
|
.flatMap {
|
|
_ ?? { ZulipLink =>
|
|
noteApi.write(
|
|
user,
|
|
s"$domain discussion: $ZulipLink",
|
|
mod.user,
|
|
modOnly = true,
|
|
dox = (domain == ModDomain.Admin)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
def userModNote(modName: String, username: String, note: String): Funit =
|
|
zulip(_.mod.adminLog, "notes")(
|
|
s"${markdown.modLink(modName)} :note: **${markdown.userLink(username)}** (${markdown.userNotesLink(username)}):\n" +
|
|
markdown.linkifyUsers(note take 2000)
|
|
)
|
|
|
|
def selfReport(typ: String, path: String, user: User, ip: IpAddress): Funit =
|
|
zulip(_.mod.adminLog, "self report")(
|
|
s"[**$typ**] ${markdown.userLink(user)}@$ip ${markdown.gameLink(path)}"
|
|
)
|
|
|
|
def commlog(mod: Holder, user: User, reportBy: Option[User.ID]): Funit =
|
|
zulip(_.mod.adminLog, "private comms checks")({
|
|
val finalS = if (user.username endsWith "s") "" else "s"
|
|
s"**${markdown modLink mod.user}** checked out **${markdown userLink user.username}**'$finalS communications "
|
|
} + reportBy.filter(mod.id !=).fold("spontaneously") { by =>
|
|
s"while investigating a report created by ${markdown.userLink(by)}"
|
|
})
|
|
|
|
def monitorMod(modId: User.ID, icon: String, text: String, tpe: ModDomain): Funit =
|
|
lightUser(modId) flatMap {
|
|
_ ?? { mod =>
|
|
zulip(_.mod.adminMonitor(tpe), mod.name)(
|
|
s"${markdown.userLink(mod.name)} :$icon: ${markdown.linkifyPostsAndUsers(text)}"
|
|
)
|
|
}
|
|
}
|
|
|
|
def logMod(modId: User.ID, icon: String, text: String): Funit =
|
|
lightUser(modId) flatMap {
|
|
_ ?? { mod =>
|
|
zulip(_.mod.log, "actions")(
|
|
s"${markdown.modLink(modId)} :$icon: ${markdown.linkifyPostsAndUsers(text)}"
|
|
)
|
|
}
|
|
}
|
|
|
|
// def printBan(mod: Holder, print: String, userIds: List[User.ID]): Funit =
|
|
// logMod(mod.id, "footprints", s"Ban print $print of ${userIds} users: ${userIds map linkifyUsers}")
|
|
|
|
def chatPanic(mod: Holder, v: Boolean): Funit = {
|
|
val msg =
|
|
s":stop: ${markdown.modLink(mod.user)} ${if (v) "enabled" else "disabled"} ${markdown.lichessLink("/mod/chat-panic", " Chat Panic")}"
|
|
zulip(_.mod.log, "chat panic")(msg) >> zulip(_.mod.commsPublic, "main")(msg)
|
|
}
|
|
|
|
def garbageCollector(msg: String): Funit =
|
|
zulip(_.mod.adminLog, "garbage collector")(markdown linkifyUsers msg)
|
|
|
|
def broadcastError(id: String, name: String, error: String): Funit =
|
|
zulip(_.broadcast, "lila error log")(s"${markdown.broadcastLink(id, name)} $error")
|
|
|
|
def ublogPost(
|
|
user: User,
|
|
id: String,
|
|
slug: String,
|
|
title: String,
|
|
intro: String
|
|
): Funit =
|
|
zulip(_.blog, "non-tiered new posts")(
|
|
s":note: ${markdown
|
|
.lichessLink(s"/@/${user.username}/blog/$slug/$id", title)} $intro - by ${markdown
|
|
.userLink(user)}"
|
|
)
|
|
|
|
def userAppeal(user: User, mod: Holder): Funit =
|
|
zulip
|
|
.sendAndGetLink(_.mod.adminAppeal, "/" + user.username)(
|
|
s"${markdown.modLink(mod.user)} :monkahmm: is looking at the appeal of **${markdown
|
|
.lichessLink(s"/appeal/${user.username}", user.username)}**"
|
|
)
|
|
.flatMap {
|
|
_ ?? { zulipAppealConv =>
|
|
noteApi.write(user, s"Appeal discussion: $zulipAppealConv", mod.user, modOnly = true, dox = true)
|
|
}
|
|
}
|
|
|
|
def nameClosePreset(username: String): Funit =
|
|
zulip(_.mod.commsPublic, "/" + username)("@**remind** here in 48h to close this account")
|
|
|
|
def stop(): Funit = zulip(_.general, "lila")("Lichess is restarting.")
|
|
|
|
def publishEvent(event: Event): Funit = event match {
|
|
case Error(msg) => publishError(msg)
|
|
case Warning(msg) => publishWarning(msg)
|
|
case Info(msg) => publishInfo(msg)
|
|
case Victory(msg) => publishVictory(msg)
|
|
}
|
|
|
|
def signupAfterTryingDisposableEmail(user: User, email: EmailAddress, previous: Set[EmailAddress]) =
|
|
zulip(_.mod.adminLog, "disposable email")(
|
|
s"${markdown userLink user} signed up with ${email.value} after trying: ${previous.map(_.value) mkString ", "}"
|
|
)
|
|
|
|
private def publishError(msg: String): Funit =
|
|
zulip(_.general, "lila")(s":lightning: ${markdown linkifyUsers msg}")
|
|
|
|
private def publishWarning(msg: String): Funit =
|
|
zulip(_.general, "lila")(s":thinking: ${markdown linkifyUsers msg}")
|
|
|
|
private def publishVictory(msg: String): Funit =
|
|
zulip(_.general, "lila")(s":tada: ${markdown linkifyUsers msg}")
|
|
|
|
private[irc] def publishInfo(msg: String): Funit =
|
|
zulip(_.general, "lila")(s":info: ${markdown linkifyUsers msg}")
|
|
|
|
object charge {
|
|
import lila.hub.actorApi.plan.ChargeEvent
|
|
private var buffer: Vector[ChargeEvent] = Vector.empty
|
|
implicit private val amountOrdering = Ordering.by[ChargeEvent, Int](_.cents)
|
|
|
|
def apply(event: ChargeEvent): Funit = {
|
|
buffer = buffer :+ event
|
|
buffer.head.date.isBefore(DateTime.now.minusHours(12)) ?? {
|
|
val firsts = Heapsort.topN(buffer, 10, amountOrdering).map(_.username).map(userAt).mkString(", ")
|
|
val amountSum = buffer.map(_.cents).sum
|
|
val patrons =
|
|
if (firsts.lengthIs > 10) s"$firsts and, like, ${firsts.length - 10} others,"
|
|
else firsts
|
|
displayMessage {
|
|
s"$patrons donated ${amount(amountSum)}. Monthly progress: ${buffer.last.percent}%"
|
|
} >>- {
|
|
buffer = Vector.empty
|
|
}
|
|
}
|
|
}
|
|
|
|
private def displayMessage(text: String) =
|
|
zulip(_.general, "lila")(markdown.linkifyUsers(text))
|
|
|
|
private def userAt(username: String) =
|
|
if (username == "Anonymous") "Anonymous"
|
|
else s"@$username"
|
|
|
|
private def amount(cents: Int) = s"$$${BigDecimal(cents.toLong, 2)}"
|
|
}
|
|
}
|
|
|
|
object IrcApi {
|
|
|
|
sealed trait ModDomain {
|
|
def key = toString.toLowerCase
|
|
}
|
|
object ModDomain {
|
|
case object Admin extends ModDomain
|
|
case object Hunt extends ModDomain
|
|
case object Comm extends ModDomain
|
|
case object Other extends ModDomain
|
|
}
|
|
|
|
private val userRegex = lila.common.String.atUsernameRegex.pattern
|
|
private val postRegex = lila.common.String.forumPostPathRegex.pattern
|
|
|
|
private object markdown {
|
|
def link(url: String, name: String) = s"[$name]($url)"
|
|
def lichessLink(path: String, name: String) = s"[$name](https://lichess.org$path)"
|
|
def userLink(name: String): String = lichessLink(s"/@/$name?mod¬es", name)
|
|
def userLink(user: User): String = userLink(user.username)
|
|
def modLink(name: String): String = lichessLink(s"/@/$name", name)
|
|
def modLink(user: User): String = modLink(user.username)
|
|
def gameLink(id: String) = lichessLink(s"/$id", s"#$id")
|
|
def userNotesLink(name: String) = lichessLink(s"/@/$name?notes", "notes")
|
|
def broadcastLink(id: String, name: String) = lichessLink(s"/broadcast/-/$id", name)
|
|
val userReplace = link("https://lichess.org/@/$1?mod", "$1")
|
|
def linkifyUsers(msg: String) = userRegex matcher msg replaceAll userReplace
|
|
val postReplace = lichessLink("/forum/$1", "$1")
|
|
def linkifyPosts(msg: String) = postRegex matcher msg replaceAll postReplace
|
|
def linkifyPostsAndUsers(msg: String) = linkifyPosts(linkifyUsers(msg))
|
|
def fixImageUrl(url: String) = url.replace("/display?", "/display.jpg?")
|
|
}
|
|
}
|