add discord integration - closes lichess-org/tavern#26
parent
eda37f5261
commit
f6f5beb38a
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -445,6 +445,7 @@ simulation {
|
|||
watchers = 200
|
||||
}
|
||||
slack.incoming.url = ""
|
||||
discord.webhook.url = ""
|
||||
plan {
|
||||
stripe {
|
||||
endpoint="https://api.stripe.com/v1"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue