msg shadowban
This commit is contained in:
parent
596574104d
commit
d31ea7d929
|
@ -1,8 +1,10 @@
|
|||
package lila.msg
|
||||
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
import lila.user.User
|
||||
import lila.db.dsl._
|
||||
import lila.db.BSON
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
private object BsonHandlers {
|
||||
|
||||
|
@ -37,4 +39,9 @@ private object BsonHandlers {
|
|||
"_id" -> ornicar.scalalib.Random.nextString(10),
|
||||
"tid" -> threadId
|
||||
)
|
||||
|
||||
def writeThread(thread: MsgThread, delBy: Option[User.ID]): Bdoc =
|
||||
threadHandler.writeTry(thread).get ++ delBy.?? { by =>
|
||||
$doc("del" -> List(by))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ final class Env(
|
|||
|
||||
private lazy val notifier = wire[MsgNotify]
|
||||
|
||||
private lazy val security = wire[MsgSecurity]
|
||||
|
||||
lazy val api: MsgApi = wire[MsgApi]
|
||||
|
||||
lazy val search = wire[MsgSearch]
|
||||
|
|
|
@ -1,42 +1,26 @@
|
|||
package lila.msg
|
||||
|
||||
import reactivemongo.api._
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import reactivemongo.api._
|
||||
import scala.concurrent.duration._
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.common.{ Bus, LightUser }
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
import lila.memo.RateLimit
|
||||
|
||||
final class MsgApi(
|
||||
colls: MsgColls,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
lightUserApi: lila.user.LightUserApi,
|
||||
relationApi: lila.relation.RelationApi,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
json: MsgJson,
|
||||
notifier: MsgNotify
|
||||
notifier: MsgNotify,
|
||||
security: MsgSecurity
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import BsonHandlers._
|
||||
|
||||
private val CreateLimitPerUser = new RateLimit[User.ID](
|
||||
credits = 20,
|
||||
duration = 24 hour,
|
||||
name = "PM creates per user",
|
||||
key = "msg_create.user"
|
||||
)
|
||||
|
||||
private val ReplyLimitPerUser = new RateLimit[User.ID](
|
||||
credits = 20,
|
||||
duration = 1 minute,
|
||||
name = "PM replies per user",
|
||||
key = "msg_reply.user"
|
||||
)
|
||||
|
||||
def threadsOf(me: User): Fu[List[MsgThread]] =
|
||||
colls.thread.ext
|
||||
.find($doc("users" -> me.id, "del" $ne me.id))
|
||||
|
@ -51,7 +35,7 @@ final class MsgApi(
|
|||
_ <- setReadBy(threadId, me)
|
||||
msgs <- threadMsgsFor(threadId, me)
|
||||
relations <- relationApi.fetchRelations(me.id, userId)
|
||||
postable <- canPost(me.id, userId)
|
||||
postable <- security.may.post(me.id, userId)
|
||||
} yield MsgConvo(contact, msgs, relations, postable)
|
||||
}
|
||||
|
||||
|
@ -66,62 +50,43 @@ final class MsgApi(
|
|||
|
||||
val postForm = Form(single("text" -> nonEmptyText(maxLength = 10_000)))
|
||||
|
||||
private[msg] def post(orig: User.ID, dest: User.ID, text: String): Funit = canPost(orig, dest) flatMap {
|
||||
_ ?? {
|
||||
val msg = Msg.make(text, orig)
|
||||
val threadId = MsgThread.id(orig, dest)
|
||||
!colls.thread.exists($id(threadId)) flatMap { isNew =>
|
||||
val msgWrite = colls.msg.insert.one(writeMsg(msg, threadId))
|
||||
val threadWrite =
|
||||
if (isNew)
|
||||
colls.thread.insert.one(MsgThread.make(orig, dest, msg)).void
|
||||
else
|
||||
colls.thread.update
|
||||
.one(
|
||||
$id(threadId),
|
||||
$set("lastMsg" -> msg.asLast) ++ $unset("del")
|
||||
)
|
||||
.void
|
||||
(msgWrite zip threadWrite).void >>- {
|
||||
notifier.onPost(threadId)
|
||||
Bus.publish(
|
||||
lila.hub.actorApi.socket.SendTo(
|
||||
dest,
|
||||
lila.socket.Socket.makeMessage("msgNew", json.renderMsg(msg))
|
||||
),
|
||||
"socketUsers"
|
||||
)
|
||||
}
|
||||
private[msg] def post(orig: User.ID, dest: User.ID, text: String): Funit = {
|
||||
val msg = Msg.make(text, orig)
|
||||
val threadId = MsgThread.id(orig, dest)
|
||||
!colls.thread.exists($id(threadId)) flatMap { isNew =>
|
||||
security.post(orig, dest, msg, isNew) flatMap {
|
||||
case MsgSecurity.Ok(mute) =>
|
||||
val msgWrite = colls.msg.insert.one(writeMsg(msg, threadId))
|
||||
val threadWrite =
|
||||
if (isNew)
|
||||
colls.thread.insert.one {
|
||||
writeThread(MsgThread.make(orig, dest, msg), delBy = mute option dest)
|
||||
}.void
|
||||
else
|
||||
colls.thread.update
|
||||
.one(
|
||||
$id(threadId),
|
||||
$set("lastMsg" -> msg.asLast) ++ $pull(
|
||||
// unset deleted by receiver unless the message is muted
|
||||
"del" $in (orig :: (!mute).option(dest).toList)
|
||||
)
|
||||
)
|
||||
.void
|
||||
(msgWrite zip threadWrite).void >>- {
|
||||
notifier.onPost(threadId)
|
||||
Bus.publish(
|
||||
lila.hub.actorApi.socket.SendTo(
|
||||
dest,
|
||||
lila.socket.Socket.makeMessage("msgNew", json.renderMsg(msg))
|
||||
),
|
||||
"socketUsers"
|
||||
)
|
||||
}
|
||||
case _ => funit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[msg] def canPost(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
!relationApi.fetchBlocks(dest, orig) >>& {
|
||||
canCreate(orig, dest) >>| canReply(orig, dest)
|
||||
}
|
||||
|
||||
private def canCreate(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
prefApi.getPref(dest, _.message) flatMap {
|
||||
case lila.pref.Pref.Message.NEVER => fuccess(false)
|
||||
case lila.pref.Pref.Message.FRIEND => relationApi.fetchFollows(dest, orig)
|
||||
case lila.pref.Pref.Message.ALWAYS => fuccess(true)
|
||||
}
|
||||
|
||||
// Even if the dest prefs disallow it,
|
||||
// you can still reply if they recently messaged you,
|
||||
// unless they deleted the thread.
|
||||
private def canReply(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
colls.thread.exists(
|
||||
$id(MsgThread.id(orig, dest)) ++ $or(
|
||||
"del" $ne dest,
|
||||
$doc(
|
||||
"lastMsg.user" -> dest,
|
||||
"lastMsg.date" $gt DateTime.now.minusDays(3)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def setRead(userId: User.ID, contactId: User.ID): Funit = {
|
||||
val threadId = MsgThread.id(userId, contactId)
|
||||
colls.thread
|
||||
|
|
|
@ -40,15 +40,18 @@ final private class MsgNotify(
|
|||
private def doNotify(threadId: MsgThread.Id): Funit =
|
||||
colls.thread.byId[MsgThread](threadId.value) flatMap {
|
||||
_ ?? { thread =>
|
||||
val msg = thread.lastMsg
|
||||
lila.common.Bus.publish(MsgThread.Unread(thread), "msgUnread")
|
||||
notifyApi addNotification Notification.make(
|
||||
Notification.Notifies(thread other msg.user),
|
||||
PrivateMessage(
|
||||
PrivateMessage.Sender(msg.user),
|
||||
PrivateMessage.Text(shorten(msg.text, 80))
|
||||
val msg = thread.lastMsg
|
||||
val dest = thread other msg.user
|
||||
!thread.delBy(dest) ?? {
|
||||
lila.common.Bus.publish(MsgThread.Unread(thread), "msgUnread")
|
||||
notifyApi addNotification Notification.make(
|
||||
Notification.Notifies(dest),
|
||||
PrivateMessage(
|
||||
PrivateMessage.Sender(msg.user),
|
||||
PrivateMessage.Text(shorten(msg.text, 80))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
88
modules/msg/src/main/MsgSecurity.scala
Normal file
88
modules/msg/src/main/MsgSecurity.scala
Normal file
|
@ -0,0 +1,88 @@
|
|||
package lila.msg
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.memo.RateLimit
|
||||
import lila.user.User
|
||||
|
||||
final private class MsgSecurity(
|
||||
colls: MsgColls,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
userRepo: lila.user.UserRepo,
|
||||
relationApi: lila.relation.RelationApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import BsonHandlers._
|
||||
import MsgSecurity._
|
||||
|
||||
private val CreateLimitPerUser = new RateLimit[User.ID](
|
||||
credits = 20,
|
||||
duration = 24 hour,
|
||||
name = "PM creates per user",
|
||||
key = "msg_create.user"
|
||||
)
|
||||
|
||||
private val ReplyLimitPerUser = new RateLimit[User.ID](
|
||||
credits = 20,
|
||||
duration = 1 minute,
|
||||
name = "PM replies per user",
|
||||
key = "msg_reply.user"
|
||||
)
|
||||
|
||||
def post(orig: User.ID, dest: User.ID, msg: Msg, isNew: Boolean): Fu[Verdict] =
|
||||
may.post(orig, dest) flatMap {
|
||||
case false => fuccess(Block)
|
||||
case _ =>
|
||||
val limiter = if (isNew) CreateLimitPerUser else ReplyLimitPerUser
|
||||
if (!limiter(orig)(true)) fuccess(Limit)
|
||||
else
|
||||
muteTroll(orig, dest) map { troll =>
|
||||
Ok(mute = troll)
|
||||
}
|
||||
}
|
||||
|
||||
private def muteTroll(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
userRepo.isTroll(orig) >>&
|
||||
!userRepo.isTroll(dest) >>&
|
||||
!relationApi.fetchFollows(dest, orig)
|
||||
|
||||
object may {
|
||||
|
||||
def post(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
!relationApi.fetchBlocks(dest, orig) >>& {
|
||||
create(orig, dest) >>| reply(orig, dest)
|
||||
}
|
||||
|
||||
private def create(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
prefApi.getPref(dest, _.message) flatMap {
|
||||
case lila.pref.Pref.Message.NEVER => fuccess(false)
|
||||
case lila.pref.Pref.Message.FRIEND => relationApi.fetchFollows(dest, orig)
|
||||
case lila.pref.Pref.Message.ALWAYS => fuccess(true)
|
||||
}
|
||||
|
||||
// Even if the dest prefs disallow it,
|
||||
// you can still reply if they recently messaged you,
|
||||
// unless they deleted the thread.
|
||||
private def reply(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
colls.thread.exists(
|
||||
$id(MsgThread.id(orig, dest)) ++ $or(
|
||||
"del" $ne dest,
|
||||
$doc(
|
||||
"lastMsg.user" -> dest,
|
||||
"lastMsg.date" $gt DateTime.now.minusDays(3)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private object MsgSecurity {
|
||||
|
||||
sealed trait Verdict
|
||||
|
||||
case class Ok(mute: Boolean) extends Verdict
|
||||
case object Block extends Verdict
|
||||
case object Limit extends Verdict
|
||||
}
|
|
@ -7,7 +7,8 @@ case class MsgThread(
|
|||
id: MsgThread.Id,
|
||||
user1: User.ID,
|
||||
user2: User.ID,
|
||||
lastMsg: Msg.Last
|
||||
lastMsg: Msg.Last,
|
||||
del: Option[List[User.ID]] = None
|
||||
) {
|
||||
|
||||
def users = List(user1, user2)
|
||||
|
@ -15,6 +16,8 @@ case class MsgThread(
|
|||
def other(userId: User.ID): User.ID = if (user1 == userId) user2 else user1
|
||||
def other(user: User): User.ID = other(user.id)
|
||||
def other(user: LightUser): User.ID = other(user.id)
|
||||
|
||||
def delBy(userId: User.ID) = del.exists(_ contains userId)
|
||||
}
|
||||
|
||||
object MsgThread {
|
||||
|
|
Loading…
Reference in a new issue