2021-06-10 03:03:47 -06:00
|
|
|
package lila.irc
|
|
|
|
|
|
|
|
import play.api.libs.json._
|
|
|
|
import play.api.libs.ws.DefaultBodyWritables._
|
2021-07-22 23:59:37 -06:00
|
|
|
import play.api.libs.ws.JsonBodyReadables._
|
2021-06-10 03:03:47 -06:00
|
|
|
import play.api.libs.ws.StandaloneWSClient
|
|
|
|
import play.api.libs.ws.WSAuthScheme
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
|
|
|
import lila.common.config.Secret
|
2021-07-22 23:59:37 -06:00
|
|
|
import lila.common.String.urlencode
|
2021-06-10 03:03:47 -06:00
|
|
|
import lila.memo.RateLimit
|
|
|
|
|
|
|
|
final private class ZulipClient(ws: StandaloneWSClient, config: ZulipClient.Config)(implicit
|
|
|
|
ec: scala.concurrent.ExecutionContext
|
|
|
|
) {
|
|
|
|
|
2021-06-22 09:27:02 -06:00
|
|
|
private val limiter = new RateLimit[Int](
|
2021-06-10 03:03:47 -06:00
|
|
|
credits = 1,
|
|
|
|
duration = 15 minutes,
|
|
|
|
key = "zulip.client"
|
|
|
|
)
|
|
|
|
|
2021-07-08 12:49:21 -06:00
|
|
|
def apply(stream: ZulipClient.stream.Selector, topic: String): String => Funit =
|
2021-07-22 23:59:37 -06:00
|
|
|
apply(stream(ZulipClient.stream), topic)(_).void
|
2021-07-08 12:49:21 -06:00
|
|
|
|
|
|
|
def apply(stream: String, topic: String)(content: String): Funit =
|
2021-07-22 23:59:37 -06:00
|
|
|
send(ZulipMessage(stream = stream, topic = topic, content = content)).void
|
2021-06-10 03:03:47 -06:00
|
|
|
|
2021-07-23 00:09:32 -06:00
|
|
|
def sendAndGetLink(stream: ZulipClient.stream.Selector, topic: String)(
|
|
|
|
content: String
|
|
|
|
): Fu[Option[String]] = {
|
|
|
|
val streamString = stream(ZulipClient.stream)
|
2021-07-22 23:59:37 -06:00
|
|
|
send(
|
|
|
|
ZulipMessage(stream = streamString, topic = topic, content = content),
|
|
|
|
returnMsgId = true
|
|
|
|
).map {
|
|
|
|
// Can be `None` if the message was rate-limited (i.e Someone already created a conv a few minutes earlier)
|
|
|
|
_.map(msgId =>
|
2021-07-23 00:09:32 -06:00
|
|
|
s"https://${config.domain}/#narrow/stream/${urlencode(streamString)}/topic/${urlencode(topic)}/near/$msgId"
|
2021-07-22 23:59:37 -06:00
|
|
|
)
|
2021-07-23 00:09:32 -06:00
|
|
|
}
|
|
|
|
}
|
2021-07-22 23:59:37 -06:00
|
|
|
|
|
|
|
private def send(msg: ZulipMessage, returnMsgId: Boolean = false): Fu[Option[ZulipMessage.ID]] =
|
2021-06-22 09:27:02 -06:00
|
|
|
limiter(msg.hashCode) {
|
2021-07-22 23:59:37 -06:00
|
|
|
if (config.domain.isEmpty) fuccess(lila.log("zulip").info(msg.toString)) >> fuccess(None)
|
2021-07-06 00:24:26 -06:00
|
|
|
else {
|
2021-07-22 23:59:37 -06:00
|
|
|
val msgId = ws
|
|
|
|
.url(s"https://${config.domain}/api/v1/messages")
|
2021-06-10 03:03:47 -06:00
|
|
|
.withAuth(config.user, config.pass.value, WSAuthScheme.BASIC)
|
|
|
|
.post(
|
|
|
|
Map(
|
|
|
|
"type" -> "stream",
|
|
|
|
"to" -> msg.stream,
|
|
|
|
"topic" -> msg.topic,
|
|
|
|
"content" -> msg.content
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.flatMap {
|
2021-07-22 23:59:37 -06:00
|
|
|
case res if res.status == 200 =>
|
|
|
|
(res.body[JsValue] \ "id").validate[ZulipMessage.ID] match {
|
|
|
|
case JsSuccess(result, _) => fuccess(result.some)
|
|
|
|
case JsError(err) => fufail(s"[zulip]: $err, $msg ${res.status} ${res.body}")
|
|
|
|
}
|
|
|
|
case res => fufail(s"[zulip] $msg ${res.status} ${res.body}")
|
2021-06-10 03:03:47 -06:00
|
|
|
}
|
2021-07-05 01:22:54 -06:00
|
|
|
.monSuccess(_.irc.zulip.say(msg.stream))
|
2021-07-05 05:55:14 -06:00
|
|
|
.logFailure(lila.log("zulip"))
|
2021-06-10 03:03:47 -06:00
|
|
|
.nevermind
|
2021-07-22 23:59:37 -06:00
|
|
|
if (returnMsgId) {
|
|
|
|
msgId
|
|
|
|
} else {
|
|
|
|
fuccess(None) // return immediately! don't make mod actions slower because of that
|
|
|
|
}
|
2021-07-06 00:24:26 -06:00
|
|
|
}
|
2021-07-22 23:59:37 -06:00
|
|
|
}(fuccess(None))
|
2021-06-10 03:03:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
private object ZulipClient {
|
|
|
|
|
|
|
|
case class Config(domain: String, user: String, pass: Secret)
|
|
|
|
import io.methvin.play.autoconfig._
|
|
|
|
implicit val zulipConfigLoader = AutoConfig.loader[Config]
|
|
|
|
|
|
|
|
object stream {
|
2021-07-02 02:03:41 -06:00
|
|
|
object mod {
|
2021-07-08 12:49:21 -06:00
|
|
|
val log = "mod-log"
|
|
|
|
val adminLog = "mod-admin-log"
|
|
|
|
val adminGeneral = "mod-admin-general"
|
|
|
|
val commsPrivate = "mod-comms-private"
|
|
|
|
val hunterCheat = "mod-hunter-cheat"
|
2021-07-22 23:59:37 -06:00
|
|
|
val adminAppeal = "mod-admin-appeal"
|
2021-07-08 12:49:21 -06:00
|
|
|
def adminMonitor(tpe: IrcApi.ModDomain) = s"mod-admin-monitor-${tpe.key}"
|
2021-07-02 02:03:41 -06:00
|
|
|
}
|
|
|
|
val general = "general"
|
2021-07-05 01:22:54 -06:00
|
|
|
val broadcast = "content-broadcast"
|
2021-07-02 02:03:41 -06:00
|
|
|
type Selector = ZulipClient.stream.type => String
|
2021-06-10 03:03:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private case class ZulipMessage(
|
|
|
|
stream: String,
|
|
|
|
topic: String,
|
|
|
|
content: String
|
|
|
|
) {
|
|
|
|
|
2021-07-08 12:49:21 -06:00
|
|
|
override def toString = s"[$stream:$topic] $content"
|
2021-06-10 03:03:47 -06:00
|
|
|
}
|
2021-07-22 23:59:37 -06:00
|
|
|
|
|
|
|
private object ZulipMessage {
|
|
|
|
type ID = Int
|
|
|
|
}
|