better report muted threads
parent
8ba91adcf5
commit
b39a1fdc28
|
@ -39,13 +39,16 @@ package socket {
|
|||
def apply[A: Writes](userIds: Set[String], typ: String, data: A): SendTos =
|
||||
SendTos(userIds, Json.obj("t" -> typ, "d" -> data))
|
||||
}
|
||||
case class RemoteSocketTellSriIn(sri: String, user: Option[String], msg: JsObject)
|
||||
case class RemoteSocketTellSriOut(sri: String, payload: JsValue)
|
||||
object remote {
|
||||
case class TellSriIn(sri: String, user: Option[String], msg: JsObject)
|
||||
case class TellSriOut(sri: String, payload: JsValue)
|
||||
case class ConnectUser(userId: String)
|
||||
}
|
||||
}
|
||||
|
||||
package report {
|
||||
case class Cheater(userId: String, text: String)
|
||||
case class Shutup(userId: String, text: String)
|
||||
case class Shutup(userId: String, text: String, major: Boolean)
|
||||
case class Booster(winnerId: String, loserId: String)
|
||||
}
|
||||
|
||||
|
@ -58,7 +61,7 @@ package security {
|
|||
package shutup {
|
||||
case class RecordPublicForumMessage(userId: String, text: String)
|
||||
case class RecordTeamForumMessage(userId: String, text: String)
|
||||
case class RecordPrivateMessage(userId: String, toUserId: String, text: String)
|
||||
case class RecordPrivateMessage(userId: String, toUserId: String, text: String, muted: Boolean)
|
||||
case class RecordPrivateChat(chatId: String, userId: String, text: String)
|
||||
case class RecordPublicChat(userId: String, text: String, source: PublicSource)
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ final class MessageApi(
|
|||
sendUnlessBlocked(thread, fromMod) flatMap {
|
||||
_ ?? {
|
||||
val text = s"${data.subject} ${data.text}"
|
||||
shutup ! lila.hub.actorApi.shutup.RecordPrivateMessage(me.id, invited.id, text)
|
||||
shutup ! lila.hub.actorApi.shutup.RecordPrivateMessage(me.id, invited.id, text, thread.looksMuted)
|
||||
notify(thread)
|
||||
}
|
||||
} inject thread
|
||||
|
@ -99,7 +99,7 @@ final class MessageApi(
|
|||
val newThread = thread + post
|
||||
coll.update($id(newThread.id), newThread) >> {
|
||||
val toUserId = newThread otherUserId me
|
||||
shutup ! lila.hub.actorApi.shutup.RecordPrivateMessage(me.id, toUserId, text)
|
||||
shutup ! lila.hub.actorApi.shutup.RecordPrivateMessage(me.id, toUserId, text, muted = false)
|
||||
notify(thread, post)
|
||||
} inject newThread
|
||||
}
|
||||
|
|
|
@ -6,15 +6,15 @@ import lila.shutup.Analyser
|
|||
import lila.user.User
|
||||
|
||||
private[message] final class MessageSecurity(
|
||||
follows: (String, String) => Fu[Boolean],
|
||||
blocks: (String, String) => Fu[Boolean],
|
||||
getPref: String => Fu[lila.pref.Pref],
|
||||
follows: (User.ID, User.ID) => Fu[Boolean],
|
||||
blocks: (User.ID, User.ID) => Fu[Boolean],
|
||||
getPref: User.ID => Fu[lila.pref.Pref],
|
||||
spam: lila.security.Spam
|
||||
) {
|
||||
|
||||
import lila.pref.Pref.Message._
|
||||
|
||||
def canMessage(from: String, to: String): Fu[Boolean] =
|
||||
def canMessage(from: User.ID, to: User.ID): Fu[Boolean] =
|
||||
blocks(to, from) flatMap {
|
||||
case true => fuFalse
|
||||
case false => getPref(to).map(_.message) flatMap {
|
||||
|
@ -27,7 +27,7 @@ private[message] final class MessageSecurity(
|
|||
def muteThreadIfNecessary(thread: Thread, creator: User, invited: User): Fu[Thread] = {
|
||||
val fullText = s"${thread.name} ${~thread.firstPost.map(_.text)}"
|
||||
if (spam.detect(fullText)) {
|
||||
logger.warn(s"PM spam from ${creator.username}: fullText")
|
||||
logger.warn(s"PM spam from ${creator.username}: $fullText")
|
||||
fuTrue
|
||||
} else if (creator.troll) !follows(invited.id, creator.id)
|
||||
else if (Analyser(fullText).dirty && creator.createdAt.isAfter(DateTime.now.minusDays(30))) {
|
||||
|
|
|
@ -87,13 +87,15 @@ case class Thread(
|
|||
|
||||
def deleteFor(user: User) = copy(
|
||||
visibleByUserIds = visibleByUserIds filter (user.id !=),
|
||||
deletedByUserIds = Some(deletedByUserIds.getOrElse(List()) ::: List(user.id))
|
||||
deletedByUserIds = Some(user.id :: ~deletedByUserIds)
|
||||
)
|
||||
|
||||
def isVisibleBy(userId: User.ID) = visibleByUserIds contains userId
|
||||
|
||||
def isVisibleByOther(user: User) = isVisibleBy(otherUserId(user))
|
||||
|
||||
def looksMuted = posts.length == 1 && (~deletedByUserIds).has(invitedId)
|
||||
|
||||
def hasPostsWrittenBy(userId: User.ID) = posts exists (_.isByCreator == (creatorId == userId))
|
||||
|
||||
def endsWith(post: Post) = posts.lastOption ?? post.similar
|
||||
|
|
|
@ -55,8 +55,8 @@ final class Env(
|
|||
def receive = {
|
||||
case lila.hub.actorApi.report.Cheater(userId, text) =>
|
||||
api.autoCheatReport(userId, text)
|
||||
case lila.hub.actorApi.report.Shutup(userId, text) =>
|
||||
api.autoInsultReport(userId, text)
|
||||
case lila.hub.actorApi.report.Shutup(userId, text, major) =>
|
||||
api.autoInsultReport(userId, text, major)
|
||||
case lila.hub.actorApi.report.Booster(winnerId, loserId) =>
|
||||
api.autoBoostReport(winnerId, loserId)
|
||||
}
|
||||
|
|
|
@ -132,6 +132,7 @@ object Report {
|
|||
|
||||
object Candidate {
|
||||
case class Scored(candidate: Candidate, score: Score) {
|
||||
def withScore(f: Score => Score) = copy(score = f(score))
|
||||
def atom = Atom(
|
||||
by = candidate.reporter.id,
|
||||
text = candidate.text,
|
||||
|
|
|
@ -20,13 +20,14 @@ final class ReportApi(
|
|||
) {
|
||||
|
||||
import BSONHandlers._
|
||||
import Report.Candidate
|
||||
|
||||
private lazy val scorer = new ReportScore(getAccuracy = accuracy.of)
|
||||
|
||||
def create(c: Report.Candidate): Funit = !c.reporter.user.reportban ?? {
|
||||
!isAlreadySlain(c) ?? {
|
||||
scorer(c) flatMap {
|
||||
case scored @ Report.Candidate.Scored(candidate, _) =>
|
||||
def create(c: Candidate, score: Report.Score => Report.Score = identity): Funit =
|
||||
(!c.reporter.user.reportban && !isAlreadySlain(c)) ?? {
|
||||
scorer(c) map (_ withScore score) flatMap {
|
||||
case scored @ Candidate.Scored(candidate, _) =>
|
||||
coll.find($doc(
|
||||
"user" -> candidate.suspect.user.id,
|
||||
"reason" -> candidate.reason,
|
||||
|
@ -39,9 +40,8 @@ final class ReportApi(
|
|||
} >>- monitorOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def commFlag(reporter: Reporter, suspect: Suspect, resource: String, text: String) = create(Report.Candidate(
|
||||
def commFlag(reporter: Reporter, suspect: Suspect, resource: String, text: String) = create(Candidate(
|
||||
reporter,
|
||||
suspect,
|
||||
Reason.CommFlag,
|
||||
|
@ -55,7 +55,7 @@ final class ReportApi(
|
|||
}
|
||||
}
|
||||
|
||||
private def isAlreadySlain(candidate: Report.Candidate) =
|
||||
private def isAlreadySlain(candidate: Candidate) =
|
||||
(candidate.isCheat && candidate.suspect.user.engine) ||
|
||||
(candidate.isAutomatic && candidate.isOther && candidate.suspect.user.troll) ||
|
||||
(candidate.isAboutComm && candidate.suspect.user.troll)
|
||||
|
@ -76,7 +76,7 @@ final class ReportApi(
|
|||
)) flatMap {
|
||||
case true => funit // only report once
|
||||
case _ => getSuspect(userId) zip getLichessReporter flatMap {
|
||||
case (Some(suspect), reporter) => create(Report.Candidate(
|
||||
case (Some(suspect), reporter) => create(Candidate(
|
||||
reporter = reporter,
|
||||
suspect = suspect,
|
||||
reason = Reason.CheatPrint,
|
||||
|
@ -92,7 +92,7 @@ final class ReportApi(
|
|||
findRecent(1, selectRecent(SuspectId(userId), Reason.Cheat)).map(_.flatMap(_.atoms.toList)) flatMap {
|
||||
case Some(suspect) ~ reporter ~ atoms if atoms.forall(_.byHuman) =>
|
||||
lila.mon.cheat.autoReport.count()
|
||||
create(Report.Candidate(
|
||||
create(Candidate(
|
||||
reporter = reporter,
|
||||
suspect = suspect,
|
||||
reason = Reason.Cheat,
|
||||
|
@ -103,7 +103,7 @@ final class ReportApi(
|
|||
|
||||
def autoBotReport(userId: String, referer: Option[String], name: String): Funit =
|
||||
getSuspect(userId) zip getLichessReporter flatMap {
|
||||
case (Some(suspect), reporter) => create(Report.Candidate(
|
||||
case (Some(suspect), reporter) => create(Candidate(
|
||||
reporter = reporter,
|
||||
suspect = suspect,
|
||||
reason = Reason.Cheat,
|
||||
|
@ -119,7 +119,7 @@ final class ReportApi(
|
|||
UserRepo.byId(userId) zip
|
||||
getLichessReporter zip
|
||||
findRecent(1, selectRecent(SuspectId(userId), Reason.Playbans)) flatMap {
|
||||
case Some(abuser) ~ reporter ~ past if past.size < 1 => create(Report.Candidate(
|
||||
case Some(abuser) ~ reporter ~ past if past.size < 1 => create(Candidate(
|
||||
reporter = reporter,
|
||||
suspect = Suspect(abuser),
|
||||
reason = Reason.Playbans,
|
||||
|
@ -140,7 +140,7 @@ final class ReportApi(
|
|||
def autoBoostReport(winnerId: User.ID, loserId: User.ID): Funit =
|
||||
securityApi.shareIpOrPrint(winnerId, loserId) zip
|
||||
UserRepo.byId(winnerId) zip UserRepo.byId(loserId) zip getLichessReporter flatMap {
|
||||
case isSame ~ Some(winner) ~ Some(loser) ~ reporter => create(Report.Candidate(
|
||||
case isSame ~ Some(winner) ~ Some(loser) ~ reporter => create(Candidate(
|
||||
reporter = reporter,
|
||||
suspect = Suspect(if (isSame) winner else loser),
|
||||
reason = Reason.Boost,
|
||||
|
@ -184,14 +184,17 @@ final class ReportApi(
|
|||
multi = true
|
||||
)
|
||||
|
||||
def autoInsultReport(userId: String, text: String): Funit = {
|
||||
def autoInsultReport(userId: String, text: String, major: Boolean): Funit = {
|
||||
getSuspect(userId) zip getLichessReporter flatMap {
|
||||
case (Some(suspect), reporter) => create(Report.Candidate(
|
||||
reporter = reporter,
|
||||
suspect = suspect,
|
||||
reason = Reason.Insult,
|
||||
text = text
|
||||
))
|
||||
case (Some(suspect), reporter) => create(
|
||||
Candidate(
|
||||
reporter = reporter,
|
||||
suspect = suspect,
|
||||
reason = Reason.Insult,
|
||||
text = text
|
||||
),
|
||||
score => if (major) Report.Score(score.value atLeast scoreThreshold()) else score
|
||||
)
|
||||
case _ => funit
|
||||
}
|
||||
} >>- monitorOpen
|
||||
|
@ -303,7 +306,7 @@ final class ReportApi(
|
|||
def of(reporter: ReporterId): Fu[Option[Accuracy]] =
|
||||
cache get reporter.value map2 Accuracy.apply
|
||||
|
||||
def apply(candidate: Report.Candidate): Fu[Option[Accuracy]] =
|
||||
def apply(candidate: Candidate): Fu[Option[Accuracy]] =
|
||||
(candidate.reason == Reason.Cheat) ?? of(candidate.reporter.id)
|
||||
|
||||
def invalidate(selector: Bdoc): Funit =
|
||||
|
@ -377,7 +380,7 @@ final class ReportApi(
|
|||
def spontaneous(mod: Mod, sus: Suspect): Fu[Report] = ofModId(mod.user.id) flatMap { current =>
|
||||
current.??(cancel(mod)) >> {
|
||||
val report = Report.make(
|
||||
Report.Candidate(
|
||||
Candidate(
|
||||
Reporter(mod.user),
|
||||
sus,
|
||||
Reason.Other,
|
||||
|
|
|
@ -30,8 +30,8 @@ final class Env(
|
|||
api.publicForumMessage(userId, text)
|
||||
case RecordTeamForumMessage(userId, text) =>
|
||||
api.teamForumMessage(userId, text)
|
||||
case RecordPrivateMessage(userId, toUserId, text) =>
|
||||
api.privateMessage(userId, toUserId, text)
|
||||
case RecordPrivateMessage(userId, toUserId, text, major) =>
|
||||
api.privateMessage(userId, toUserId, text, major)
|
||||
case RecordPrivateChat(chatId, userId, text) =>
|
||||
api.privateChat(chatId, userId, text)
|
||||
case RecordPublicChat(userId, text, source) =>
|
||||
|
|
|
@ -5,11 +5,11 @@ import reactivemongo.bson._
|
|||
import lila.db.dsl._
|
||||
import lila.game.GameRepo
|
||||
import lila.hub.actorApi.shutup.PublicSource
|
||||
import lila.user.UserRepo
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class ShutupApi(
|
||||
coll: Coll,
|
||||
follows: (String, String) => Fu[Boolean],
|
||||
follows: (User.ID, User.ID) => Fu[Boolean],
|
||||
reporter: akka.actor.ActorSelection
|
||||
) {
|
||||
|
||||
|
@ -17,27 +17,34 @@ final class ShutupApi(
|
|||
private implicit val UserRecordBSONHandler = Macros.handler[UserRecord]
|
||||
import PublicLine.PublicLineBSONHandler
|
||||
|
||||
def getPublicLines(userId: String): Fu[List[PublicLine]] =
|
||||
def getPublicLines(userId: User.ID): Fu[List[PublicLine]] =
|
||||
coll.find($doc("_id" -> userId), $doc("pub" -> 1))
|
||||
.uno[Bdoc].map {
|
||||
~_.flatMap(_.getAs[List[PublicLine]]("pub"))
|
||||
}
|
||||
|
||||
def publicForumMessage(userId: String, text: String) = record(userId, text, TextType.PublicForumMessage)
|
||||
def teamForumMessage(userId: String, text: String) = record(userId, text, TextType.TeamForumMessage)
|
||||
def publicChat(userId: String, text: String, source: PublicSource) = record(userId, text, TextType.PublicChat, source.some)
|
||||
def publicForumMessage(userId: User.ID, text: String) = record(userId, text, TextType.PublicForumMessage)
|
||||
def teamForumMessage(userId: User.ID, text: String) = record(userId, text, TextType.TeamForumMessage)
|
||||
def publicChat(userId: User.ID, text: String, source: PublicSource) = record(userId, text, TextType.PublicChat, source.some)
|
||||
|
||||
def privateChat(chatId: String, userId: String, text: String) =
|
||||
def privateChat(chatId: String, userId: User.ID, text: String) =
|
||||
GameRepo.getSourceAndUserIds(chatId) flatMap {
|
||||
case (source, _) if source.has(lila.game.Source.Friend) => funit // ignore challenges
|
||||
case (_, userIds) =>
|
||||
record(userId, text, TextType.PrivateChat, none, userIds find (userId !=))
|
||||
}
|
||||
|
||||
def privateMessage(userId: String, toUserId: String, text: String) =
|
||||
def privateMessage(userId: User.ID, toUserId: User.ID, text: String, major: Boolean) =
|
||||
record(userId, text, TextType.PrivateMessage, none, toUserId.some)
|
||||
|
||||
private def record(userId: String, text: String, textType: TextType, source: Option[PublicSource] = None, toUserId: Option[String] = None): Funit =
|
||||
private def record(
|
||||
userId: User.ID,
|
||||
text: String,
|
||||
textType: TextType,
|
||||
source: Option[PublicSource] = None,
|
||||
toUserId: Option[User.ID] = None,
|
||||
major: Boolean = false
|
||||
): Funit =
|
||||
UserRepo isTroll userId flatMap {
|
||||
case true => funit
|
||||
case false => toUserId ?? { follows(_, userId) } flatMap {
|
||||
|
@ -59,31 +66,32 @@ final class ShutupApi(
|
|||
)
|
||||
) ++ pushPublicLine
|
||||
coll.findAndUpdate(
|
||||
selector = $doc("_id" -> userId),
|
||||
update = $doc("$push" -> push),
|
||||
selector = $id(userId),
|
||||
update = $push(push),
|
||||
fetchNewObject = true,
|
||||
upsert = true
|
||||
).map(_.value) map2 UserRecordBSONHandler.read flatMap {
|
||||
case None => fufail(s"can't find user record for $userId")
|
||||
case Some(userRecord) => legiferate(userRecord)
|
||||
case Some(userRecord) => legiferate(userRecord, major)
|
||||
} logFailure lila.log("shutup")
|
||||
}
|
||||
}
|
||||
|
||||
private def legiferate(userRecord: UserRecord): Funit =
|
||||
userRecord.reports.exists(_.unacceptable) ?? {
|
||||
reporter ! lila.hub.actorApi.report.Shutup(userRecord.userId, reportText(userRecord))
|
||||
coll.update(
|
||||
$doc("_id" -> userRecord.userId),
|
||||
$doc("$unset" -> $doc(
|
||||
TextType.PublicForumMessage.key -> true,
|
||||
TextType.TeamForumMessage.key -> true,
|
||||
TextType.PrivateMessage.key -> true,
|
||||
TextType.PrivateChat.key -> true,
|
||||
TextType.PublicChat.key -> true
|
||||
))
|
||||
).void
|
||||
}
|
||||
private def legiferate(userRecord: UserRecord, major: Boolean): Funit = {
|
||||
major || userRecord.reports.exists(_.unacceptable)
|
||||
} ?? {
|
||||
reporter ! lila.hub.actorApi.report.Shutup(userRecord.userId, reportText(userRecord), major)
|
||||
coll.update(
|
||||
$id(userRecord.userId),
|
||||
$unset(
|
||||
TextType.PublicForumMessage.key,
|
||||
TextType.TeamForumMessage.key,
|
||||
TextType.PrivateMessage.key,
|
||||
TextType.PrivateChat.key,
|
||||
TextType.PublicChat.key
|
||||
)
|
||||
).void
|
||||
}
|
||||
|
||||
private def reportText(userRecord: UserRecord) =
|
||||
userRecord.reports.collect {
|
||||
|
|
Loading…
Reference in New Issue