add discord integration - closes lichess-org/tavern#26

pull/8210/head
Thibault Duplessis 2021-02-18 11:12:42 +01:00
parent eda37f5261
commit f6f5beb38a
12 changed files with 119 additions and 35 deletions

View File

@ -57,7 +57,7 @@ final class Env(
val insight: lila.insight.Env,
val push: lila.push.Env,
val perfStat: lila.perfStat.Env,
val slack: lila.irc.Env,
val irc: lila.irc.Env,
val challenge: lila.challenge.Env,
val explorer: lila.explorer.Env,
val fishnet: lila.fishnet.Env,

View File

@ -13,7 +13,7 @@ final class Dev(env: Env) extends LilaController(env) {
env.irwin.irwinThresholdsSetting,
env.explorer.indexFlowSetting,
env.report.scoreThresholdsSetting,
env.report.slackScoreThresholdSetting,
env.report.discordScoreThresholdSetting,
env.streamer.homepageMaxSetting,
env.streamer.alwaysFeaturedSetting,
env.streamer.twitchCredentialsSetting,

View File

@ -205,7 +205,7 @@ final class Mod(
def notifySlack(username: String) =
OAuthMod(_.NotifySlack) { _ => me =>
withSuspect(username) { sus =>
env.slack.api.userMod(user = sus.user, mod = me) map some
env.irc.slack.userMod(user = sus.user, mod = me) map some
}
}(actionResult(username))
@ -249,9 +249,9 @@ final class Mod(
} map { case chats ~ convos ~ publicLines ~ notes ~ history ~ inquiry ~ logins =>
if (priv) {
if (!inquiry.??(_.isRecentCommOf(Suspect(user)))) {
env.slack.api.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value))
env.irc.slack.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value))
if (isGranted(_.MonitoredMod))
env.slack.api.monitorMod(
env.irc.slack.monitorMod(
me.id,
"eyes",
s"spontaneously checked out @${user.username}'s private comms"
@ -457,7 +457,7 @@ final class Mod(
OAuthMod(_.Shadowban) { req => me =>
val v = getBool("v", req)
env.chat.panic.set(v)
env.slack.api.chatPanic(me, v)
env.irc.slack.chatPanic(me, v)
fuccess(().some)
}(_ => _ => _ => Redirect(routes.Mod.chatPanic).fuccess)

View File

@ -445,6 +445,7 @@ simulation {
watchers = 200
}
slack.incoming.url = ""
discord.webhook.url = ""
plan {
stripe {
endpoint="https://api.stripe.com/v1"

View File

@ -0,0 +1,41 @@
package lila.irc
import org.joda.time.DateTime
import lila.common.{ ApiVersion, EmailAddress, Heapsort, IpAddress, LightUser }
import lila.user.User
final class DiscordApi(
client: DiscordClient,
implicit val lightUser: LightUser.Getter
)(implicit ec: scala.concurrent.ExecutionContext) {
import DiscordApi._
def commReportBurst(user: User): Funit =
client(
DiscordMessage(
text = linkifyUsers(s"Burst of comm reports about @${user.username}"),
channel = channels.comms
)
)
private def link(url: String, name: String) = s"[$url]($name)"
private def lichessLink(path: String, name: String) = s"[https://lichess.org$path]($name)"
private def userLink(name: String): String = lichessLink(s"/@/$name?mod", name)
private def userLink(user: User): String = userLink(user.username)
private val userRegex = lila.common.String.atUsernameRegex.pattern
private val userReplace = link("https://lichess.org/@/$1?mod", "$1")
private def linkifyUsers(msg: String) =
userRegex matcher msg replaceAll userReplace
}
private object DiscordApi {
object channels {
val webhook = 672938635120869400d
val comms = 685084348096970770d
}
}

View File

@ -0,0 +1,42 @@
package lila.irc
import play.api.libs.json._
import play.api.libs.ws.JsonBodyWritables._
import play.api.libs.ws.StandaloneWSClient
import scala.concurrent.duration._
import lila.common.config.Secret
import lila.memo.RateLimit
final private class DiscordClient(ws: StandaloneWSClient, url: Secret)(implicit
ec: scala.concurrent.ExecutionContext
) {
private val defaultChannel = "webhook"
private val limiter = new RateLimit[DiscordMessage](
credits = 1,
duration = 15 minutes,
key = "discord.client"
)
def apply(msg: DiscordMessage): Funit =
limiter(msg) {
if (url.value.isEmpty) fuccess(lila.log("discord").info(msg.toString))
else
ws.url(url.value)
.post(
Json
.obj(
"content" -> msg.text,
"channel" -> msg.channel
)
.noNull
)
.flatMap {
case res if res.status == 200 => funit
case res => fufail(s"[discord] $url $msg ${res.status} ${res.body}")
}
.nevermind
}(funit)
}

View File

@ -20,20 +20,22 @@ final class Env(
mode: Mode
)(implicit ec: scala.concurrent.ExecutionContext) {
private val incomingUrl = appConfig.get[Secret]("slack.incoming.url")
private lazy val slackClient = new SlackClient(ws, appConfig.get[Secret]("slack.incoming.url"))
private lazy val client = wire[SlackClient]
private lazy val discordClient = new DiscordClient(ws, appConfig.get[Secret]("discord.webhook.url"))
lazy val api: SlackApi = wire[SlackApi]
lazy val slack: SlackApi = wire[SlackApi]
lazy val discord: DiscordApi = wire[DiscordApi]
if (mode == Mode.Prod) {
api.publishInfo("Lichess has started!")
Lilakka.shutdown(shutdown, _.PhaseBeforeServiceUnbind, "Tell slack")(api.stop _)
slack.publishInfo("Lichess has started!")
Lilakka.shutdown(shutdown, _.PhaseBeforeServiceUnbind, "Tell slack")(slack.stop _)
}
lila.common.Bus.subscribeFun("slack", "plan", "userNote") {
case d: ChargeEvent => api.charge(d).unit
case Note(from, to, text, true) if from != "Irwin" => api.userModNote(from, to, text).unit
case e: Event => api.publishEvent(e).unit
case d: ChargeEvent => slack.charge(d).unit
case Note(from, to, text, true) if from != "Irwin" => slack.userModNote(from, to, text).unit
case e: Event => slack.publishEvent(e).unit
}
}

View File

@ -153,16 +153,6 @@ final class SlackApi(
)
)
def commReportBurst(user: User): Funit =
client(
SlackMessage(
username = "Comm alert",
icon = "anger",
text = linkifyUsers(s"Burst of comm reports about @${user.username}"),
channel = rooms.tavern
)
)
def broadcastError(id: String, name: String, error: String): Funit =
client(
SlackMessage(

View File

@ -9,3 +9,11 @@ private case class SlackMessage(
override def toString = s"[$channel] :$icon: @$username: $text"
}
private case class DiscordMessage(
text: String,
channel: Double
) {
override def toString = s"[$channel] $text"
}

View File

@ -27,7 +27,7 @@ final class Env(
securityApi: lila.security.SecurityApi,
userLoginsApi: lila.security.UserLoginsApi,
playbanApi: lila.playban.PlaybanApi,
slackApi: lila.irc.SlackApi,
discordApi: lila.irc.DiscordApi,
captcher: lila.hub.actors.Captcher,
fishnet: lila.hub.actors.Fishnet,
settingStore: lila.memo.SettingStore.Builder,
@ -43,11 +43,11 @@ final class Env(
lazy val scoreThresholdsSetting = ReportThresholds makeScoreSetting settingStore
lazy val slackScoreThresholdSetting = ReportThresholds makeSlackSetting settingStore
lazy val discordScoreThresholdSetting = ReportThresholds makeDiscordSetting settingStore
private val thresholds = Thresholds(
score = scoreThresholdsSetting.get _,
slack = slackScoreThresholdSetting.get _
discord = discordScoreThresholdSetting.get _
)
lazy val forms = wire[ReportForm]

View File

@ -17,7 +17,7 @@ final class ReportApi(
securityApi: lila.security.SecurityApi,
userLoginsApi: lila.security.UserLoginsApi,
playbanApi: lila.playban.PlaybanApi,
slackApi: lila.irc.SlackApi,
discordApi: lila.irc.DiscordApi,
isOnline: lila.socket.IsOnline,
cacheApi: lila.memo.CacheApi,
thresholds: Thresholds
@ -65,9 +65,9 @@ final class ReportApi(
lila.mon.mod.report.create(report.reason.key).increment()
if (
report.isRecentComm &&
report.score.value >= thresholds.slack() &&
prev.exists(_.score.value < thresholds.slack())
) slackApi.commReportBurst(c.suspect.user)
report.score.value >= thresholds.discord() &&
prev.exists(_.score.value < thresholds.discord())
) discordApi.commReportBurst(c.suspect.user)
coll.update.one($id(report.id), report, upsert = true).void >>
autoAnalysis(candidate) >>- {
if (report.isCheat)

View File

@ -8,7 +8,7 @@ import lila.memo.SettingStore.{ Formable, StringReader }
case class ScoreThresholds(mid: Int, high: Int)
private case class Thresholds(score: () => ScoreThresholds, slack: () => Int)
private case class Thresholds(score: () => ScoreThresholds, discord: () => Int)
private object ReportThresholds {
@ -36,10 +36,10 @@ private object ReportThresholds {
text = "Report score mid and high thresholds, separated with a comma.".some
)
def makeSlackSetting(store: lila.memo.SettingStore.Builder) =
def makeDiscordSetting(store: lila.memo.SettingStore.Builder) =
store[Int](
"slackScoreThreshold",
"discordScoreThreshold",
default = 80,
text = "Slack score threshold. Comm reports with higher scores are notified in slack".some
text = "Discord score threshold. Comm reports with higher scores are notified in discord".some
)
}