try to unify chat messages / round is OK
This commit is contained in:
parent
59e1f64012
commit
c9484b7552
|
@ -36,17 +36,15 @@ trait StringHelper {
|
||||||
|
|
||||||
private val urlRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
|
private val urlRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
|
||||||
|
|
||||||
def addLinks(text: String) = urlRegex.replaceAllIn(text, m ⇒ "<a href='%s'>%s</a>".format(
|
def addLinks(text: String) = urlRegex.replaceAllIn(text, m ⇒ {
|
||||||
(prependHttp _ compose delocalize _ compose quoteReplacement _)(m group 1),
|
val url = delocalize(quoteReplacement(m group 1))
|
||||||
(delocalize _ compose quoteReplacement _)(m group 1)
|
"<a href='%s'>%s</a>".format(prependHttp(url), url)
|
||||||
))
|
})
|
||||||
|
|
||||||
private def prependHttp(url: String): String =
|
private def prependHttp(url: String): String =
|
||||||
url startsWith "http" fold(url, "http://" + url)
|
url startsWith "http" fold (url, "http://" + url)
|
||||||
|
|
||||||
private val delocalizeRegex = ("""\w+\.""" + quoteReplacement(netDomain)).r
|
private val delocalize = new lila.common.String.Delocalizer(netDomain)
|
||||||
|
|
||||||
private def delocalize(url: String) = delocalizeRegex.replaceAllIn(url, netDomain)
|
|
||||||
|
|
||||||
def showNumber(n: Int): String = (n > 0).fold("+" + n, n.toString)
|
def showNumber(n: Int): String = (n > 0).fold("+" + n, n.toString)
|
||||||
|
|
||||||
|
|
|
@ -18,20 +18,17 @@ trait UserHelper {
|
||||||
def isOnline(userId: String) = Env.user isOnline userId
|
def isOnline(userId: String) = Env.user isOnline userId
|
||||||
|
|
||||||
def userIdLink(
|
def userIdLink(
|
||||||
userId: Option[String],
|
userIdOption: Option[String],
|
||||||
cssClass: Option[String] = None,
|
cssClass: Option[String] = None,
|
||||||
withOnline: Boolean = true,
|
withOnline: Boolean = true,
|
||||||
truncate: Option[Int] = None): Html = Html {
|
truncate: Option[Int] = None): Html = Html {
|
||||||
(userId zmap Env.user.usernameOption) map {
|
userIdOption.fold(User.anonymous) { userId ⇒
|
||||||
_.fold(User.anonymous) { username ⇒
|
Env.user usernameOption userId map {
|
||||||
"""<a class="user_link%s%s" href="%s">%s</a>""".format(
|
_.fold(User.anonymous) { username ⇒
|
||||||
withOnline ?? isOnline(username).fold(" online", " offline"),
|
userIdNameLink(userId, username, cssClass, withOnline, truncate)
|
||||||
cssClass.zmap(" " + _),
|
}
|
||||||
routes.User.show(username),
|
} await
|
||||||
truncate.fold(username)(username.take)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
} await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def userIdLink(
|
def userIdLink(
|
||||||
|
@ -47,6 +44,29 @@ trait UserHelper {
|
||||||
} await
|
} await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def usernameLink(
|
||||||
|
usernameOption: Option[String],
|
||||||
|
cssClass: Option[String] = None,
|
||||||
|
withOnline: Boolean = true,
|
||||||
|
truncate: Option[Int] = None): Html = Html {
|
||||||
|
usernameOption.fold(User.anonymous) { username ⇒
|
||||||
|
userIdNameLink(username.toLowerCase, username, cssClass, withOnline, truncate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def userIdNameLink(
|
||||||
|
userId: String,
|
||||||
|
username: String,
|
||||||
|
cssClass: Option[String] = None,
|
||||||
|
withOnline: Boolean = true,
|
||||||
|
truncate: Option[Int] = None): String =
|
||||||
|
"""<a class="user_link%s%s" href="%s">%s</a>""".format(
|
||||||
|
withOnline ?? isOnline(userId).fold(" online", " offline"),
|
||||||
|
cssClass.zmap(" " + _),
|
||||||
|
routes.User.show(username),
|
||||||
|
truncate.fold(username)(username.take)
|
||||||
|
)
|
||||||
|
|
||||||
def userLink(
|
def userLink(
|
||||||
user: User,
|
user: User,
|
||||||
cssClass: Option[String] = None,
|
cssClass: Option[String] = None,
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
@base.layout(title = title) {
|
@base.layout(title = title) {
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
</style>
|
|
||||||
<div class="content_box">
|
<div class="content_box">
|
||||||
<h1>@title</h1>
|
<h1>@title</h1>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
@ -13,7 +11,7 @@
|
||||||
@messages.map { message =>
|
@messages.map { message =>
|
||||||
<tr>
|
<tr>
|
||||||
<td>@showDate(message.date)</td>
|
<td>@showDate(message.date)</td>
|
||||||
<td>@userIdLink(message.userId.some)</td>
|
<td>@userIdLink(message.user)</td>
|
||||||
<td>@Html(message.text)</td>
|
<td>@Html(message.text)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
@(messages: List[(Option[String], String)])(implicit ctx: Context)
|
@(messages: List[(Option[String], String)])(implicit ctx: Context)
|
||||||
|
|
||||||
@messages.map {
|
@messages.map {
|
||||||
case (author, text) => {
|
case (username, text) => {
|
||||||
<li>
|
<li><span>@usernameLink(username, withOnline = false, truncate = 12.some)</span>@text</li>
|
||||||
<span>
|
|
||||||
@userIdLink(author, withOnline = false, truncate = 12.some)
|
|
||||||
</span>
|
|
||||||
@text
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package lila.common
|
package lila.common
|
||||||
|
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
|
import java.util.regex.Matcher.quoteReplacement
|
||||||
|
|
||||||
object String {
|
object String {
|
||||||
|
|
||||||
|
@ -10,4 +11,11 @@ object String {
|
||||||
val slug = """[^\w-]""".r.replaceAllIn(normalized, "")
|
val slug = """[^\w-]""".r.replaceAllIn(normalized, "")
|
||||||
slug.toLowerCase
|
slug.toLowerCase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Delocalizer(netDomain: String) {
|
||||||
|
|
||||||
|
private val regex = ("""\w+\.""" + quoteReplacement(netDomain)).r
|
||||||
|
|
||||||
|
def apply(url: String) = regex.replaceAllIn(url, netDomain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,23 +114,18 @@ object Event {
|
||||||
|
|
||||||
case class Message(author: String, text: String) extends Event {
|
case class Message(author: String, text: String) extends Event {
|
||||||
def typ = "message"
|
def typ = "message"
|
||||||
def data = JsString("""<li class="%s%s">%s</li>""".format(
|
def data = Json.obj("u" -> author, "t" -> escapeXml(text))
|
||||||
author, (author == "system") ?? " trans_me", escapeXml(text)))
|
|
||||||
override def owner = true
|
override def owner = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case class WatcherMessage(author: Option[String], text: String) extends Event {
|
// it *IS* a username, and not a user ID
|
||||||
|
// immediately used for rendering
|
||||||
|
case class WatcherMessage(username: Option[String], text: String) extends Event {
|
||||||
def typ = "message"
|
def typ = "message"
|
||||||
def data = JsString(renderWatcherRoom(author, text))
|
def data = Json.obj("u" -> username, "t" -> escapeXml(text))
|
||||||
override def watcher = true
|
override def watcher = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO FIXME the username is @userId and there is no link
|
|
||||||
private def renderWatcherRoom(author: Option[String], text: String): String =
|
|
||||||
"""<li><span>%s</span>%s</li>""".format(
|
|
||||||
author.fold("Anonymous")("@" + _),
|
|
||||||
escapeXml(text))
|
|
||||||
|
|
||||||
object End extends Empty {
|
object End extends Empty {
|
||||||
def typ = "end"
|
def typ = "end"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,31 @@ import play.api.libs.json._
|
||||||
import reactivemongo.bson._
|
import reactivemongo.bson._
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
|
// it is really a username, not a user ID
|
||||||
case class Message(
|
case class Message(
|
||||||
userId: String,
|
user: Option[String],
|
||||||
text: String,
|
text: String,
|
||||||
date: DateTime) {
|
date: DateTime) {
|
||||||
|
|
||||||
def render = Json.obj("txt" -> text, "u" -> userId)
|
def render = Json.obj("u" -> user, "t" -> text)
|
||||||
|
|
||||||
def isEmpty = text.isEmpty
|
def isEmpty = text.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
object Message {
|
object Message {
|
||||||
|
|
||||||
def make(userId: String, text: String) = new Message(
|
def make(user: Option[String], text: String) = new Message(
|
||||||
userId = userId,
|
user = user,
|
||||||
text = text,
|
text = text,
|
||||||
date = DateTime.now)
|
date = DateTime.now)
|
||||||
|
|
||||||
import lila.db.Tube
|
import lila.db.Tube
|
||||||
import Tube.Helpers._
|
import Tube.Helpers._
|
||||||
|
|
||||||
|
private def defaults = Json.obj("user" -> none[String])
|
||||||
|
|
||||||
private[lobby] lazy val tube = Tube[Message](
|
private[lobby] lazy val tube = Tube[Message](
|
||||||
(__.json update readDate('date)) andThen Json.reads[Message],
|
(__.json update (merge(defaults) andThen readDate('date))) andThen Json.reads[Message],
|
||||||
Json.writes[Message] andThen (__.json update writeDate('date)),
|
Json.writes[Message] andThen (__.json update writeDate('date)),
|
||||||
flags = Seq(_.NoId))
|
flags = Seq(_.NoId))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
package lila.lobby
|
package lila.lobby
|
||||||
|
|
||||||
import lila.user.{ UserRepo, User, Room }
|
import lila.user.{ UserRepo, User, Room }
|
||||||
import lila.user.tube.userTube
|
|
||||||
import tube.messageTube
|
import tube.messageTube
|
||||||
import lila.db.api._
|
import lila.db.api._
|
||||||
|
|
||||||
private[lobby] final class Messenger(val netDomain: String) extends Room {
|
private[lobby] final class Messenger(val netDomain: String) extends Room {
|
||||||
|
|
||||||
def apply(userId: String, text: String): Fu[Message] = for {
|
def apply(userId: String, text: String): Fu[Message] = for {
|
||||||
userOption ← $find.byId[User](userId)
|
userOption ← UserRepo byId userId
|
||||||
message ← (for {
|
message ← (userMessage(userOption, text) map {
|
||||||
user ← userOption filter (_.canChat) toValid "This user cannot chat"
|
case (u, t) ⇒ Message.make(u.some, t)
|
||||||
msg ← createMessage(user, text)
|
}).future
|
||||||
(u, t) = msg
|
|
||||||
} yield Message.make(u, t)).future
|
|
||||||
_ ← $insert(message)
|
_ ← $insert(message)
|
||||||
} yield message
|
} yield message
|
||||||
|
|
||||||
def system(text: String): Fu[Message] =
|
def system(text: String): Fu[Message] =
|
||||||
Message.make(userId = "", text = text) |> { message ⇒
|
Message.make(user = none, text = text) |> { message ⇒
|
||||||
$insert(message) inject message
|
$insert(message) inject message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,13 +39,13 @@ private[lobby] final class Socket(
|
||||||
case Talk(u, txt) ⇒ messenger(u, txt) effectFold (
|
case Talk(u, txt) ⇒ messenger(u, txt) effectFold (
|
||||||
e ⇒ logwarn(e.toString),
|
e ⇒ logwarn(e.toString),
|
||||||
message ⇒ notifyVersion("talk", Json.obj(
|
message ⇒ notifyVersion("talk", Json.obj(
|
||||||
"u" -> message.userId,
|
"u" -> message.user,
|
||||||
"txt" -> message.text
|
"t" -> message.text
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
case SysTalk(txt) ⇒ messenger system txt foreach { message ⇒
|
case SysTalk(txt) ⇒ messenger system txt foreach { message ⇒
|
||||||
notifyVersion("talk", Json.obj("txt" -> message.text))
|
notifyVersion("talk", Json.obj("t" -> message.text))
|
||||||
}
|
}
|
||||||
|
|
||||||
case UnTalk(regex) ⇒ (messenger remove regex) >>-
|
case UnTalk(regex) ⇒ (messenger remove regex) >>-
|
||||||
|
|
|
@ -16,6 +16,7 @@ final class Env(
|
||||||
db: lila.db.Env,
|
db: lila.db.Env,
|
||||||
hub: lila.hub.Env,
|
hub: lila.hub.Env,
|
||||||
ai: lila.ai.Ai,
|
ai: lila.ai.Ai,
|
||||||
|
getUsername: String ⇒ Fu[Option[String]],
|
||||||
i18nKeys: lila.i18n.I18nKeys,
|
i18nKeys: lila.i18n.I18nKeys,
|
||||||
scheduler: lila.common.Scheduler) {
|
scheduler: lila.common.Scheduler) {
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ final class Env(
|
||||||
val SocketTimeout = config duration "socket.timeout"
|
val SocketTimeout = config duration "socket.timeout"
|
||||||
val FinisherLockTimeout = config duration "finisher.lock.timeout"
|
val FinisherLockTimeout = config duration "finisher.lock.timeout"
|
||||||
val HijackTimeout = config duration "hijack.timeout"
|
val HijackTimeout = config duration "hijack.timeout"
|
||||||
|
val NetDomain = config getString "net.domain"
|
||||||
}
|
}
|
||||||
import settings._
|
import settings._
|
||||||
|
|
||||||
|
@ -72,7 +74,7 @@ final class Env(
|
||||||
finisher = finisher,
|
finisher = finisher,
|
||||||
socketHub = socketHub)
|
socketHub = socketHub)
|
||||||
|
|
||||||
lazy val messenger = new Messenger(i18nKeys)
|
lazy val messenger = new Messenger(NetDomain, i18nKeys, getUsername)
|
||||||
|
|
||||||
lazy val eloCalculator = new chess.EloCalculator(false)
|
lazy val eloCalculator = new chess.EloCalculator(false)
|
||||||
|
|
||||||
|
@ -122,6 +124,7 @@ object Env {
|
||||||
db = lila.db.Env.current,
|
db = lila.db.Env.current,
|
||||||
hub = lila.hub.Env.current,
|
hub = lila.hub.Env.current,
|
||||||
ai = lila.ai.Env.current.ai,
|
ai = lila.ai.Env.current.ai,
|
||||||
|
getUsername = lila.user.Env.current.usernameOption,
|
||||||
i18nKeys = lila.i18n.Env.current.keys,
|
i18nKeys = lila.i18n.Env.current.keys,
|
||||||
scheduler = lila.common.PlayApp.scheduler)
|
scheduler = lila.common.PlayApp.scheduler)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,14 @@ import chess.Color
|
||||||
import lila.game.Event
|
import lila.game.Event
|
||||||
import tube.{ roomTube, watcherRoomTube }
|
import tube.{ roomTube, watcherRoomTube }
|
||||||
import lila.db.api._
|
import lila.db.api._
|
||||||
|
import lila.user.UserRepo
|
||||||
|
|
||||||
final class Messenger(i18nKeys: I18nKeys) {
|
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||||
|
|
||||||
|
final class Messenger(
|
||||||
|
val netDomain: String,
|
||||||
|
i18nKeys: I18nKeys,
|
||||||
|
getUsername: String ⇒ Fu[Option[String]]) extends lila.user.Room {
|
||||||
|
|
||||||
private val nbMessagesCopiedToRematch = 20
|
private val nbMessagesCopiedToRematch = 20
|
||||||
|
|
||||||
|
@ -31,7 +37,7 @@ final class Messenger(i18nKeys: I18nKeys) {
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def playerMessage(ref: PovRef, text: String): Fu[List[Event.Message]] =
|
def playerMessage(ref: PovRef, text: String): Fu[List[Event.Message]] =
|
||||||
cleanupText(text) zmap { t ⇒
|
cleanupText(text).future flatMap { t ⇒
|
||||||
RoomRepo.addMessage(ref.gameId, ref.color.name, t) inject {
|
RoomRepo.addMessage(ref.gameId, ref.color.name, t) inject {
|
||||||
Event.Message(ref.color.name, t) :: Nil
|
Event.Message(ref.color.name, t) :: Nil
|
||||||
}
|
}
|
||||||
|
@ -40,12 +46,12 @@ final class Messenger(i18nKeys: I18nKeys) {
|
||||||
def watcherMessage(
|
def watcherMessage(
|
||||||
gameId: String,
|
gameId: String,
|
||||||
userId: Option[String],
|
userId: Option[String],
|
||||||
text: String): Fu[List[Event.WatcherMessage]] =
|
text: String): Fu[List[Event.WatcherMessage]] = for {
|
||||||
cleanupText(text) zmap { t ⇒
|
userOption ← userId.zmap(UserRepo.byId)
|
||||||
WatcherRoomRepo.addMessage(gameId, userId, t) inject {
|
message ← userOrAnonMessage(userOption, text).future
|
||||||
Event.WatcherMessage(userId, text) :: Nil
|
(u, t) = message
|
||||||
}
|
_ ← WatcherRoomRepo.addMessage(gameId, u, t)
|
||||||
}
|
} yield Event.WatcherMessage(u, t) :: Nil
|
||||||
|
|
||||||
def systemMessages(game: Game, messages: List[SelectI18nKey]): Fu[List[Event]] =
|
def systemMessages(game: Game, messages: List[SelectI18nKey]): Fu[List[Event]] =
|
||||||
game.hasChat ?? {
|
game.hasChat ?? {
|
||||||
|
@ -65,11 +71,6 @@ final class Messenger(i18nKeys: I18nKeys) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def cleanupText(text: String) = {
|
|
||||||
val cleanedUp = text.trim.replace(""""""", "'")
|
|
||||||
(cleanedUp.size <= 140 && cleanedUp.nonEmpty) option cleanedUp
|
|
||||||
}
|
|
||||||
|
|
||||||
private def messageToEn(message: SelectI18nKey): String =
|
private def messageToEn(message: SelectI18nKey): String =
|
||||||
message(i18nKeys).en()
|
message(i18nKeys).en()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ object WatcherRoom {
|
||||||
Json.reads[WatcherRoom],
|
Json.reads[WatcherRoom],
|
||||||
Json.writes[WatcherRoom])
|
Json.writes[WatcherRoom])
|
||||||
|
|
||||||
def encode(userId: Option[String], text: String): String =
|
def encode(username: Option[String], text: String): String =
|
||||||
~userId + "|" + text
|
~username + "|" + text
|
||||||
|
|
||||||
def decode(encoded: String): (Option[String], String) =
|
def decode(encoded: String): (Option[String], String) =
|
||||||
encoded.span('|' !=) match {
|
encoded.span('|' !=) match {
|
||||||
case (userId, rest) ⇒ Some(userId).filter(_.nonEmpty) -> rest
|
case (username, rest) ⇒ Some(username).filter(_.nonEmpty) -> rest.drop(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ object WatcherRoomRepo {
|
||||||
|
|
||||||
def addMessage(
|
def addMessage(
|
||||||
id: String,
|
id: String,
|
||||||
userId: Option[String],
|
username: Option[String],
|
||||||
text: String): Funit = $update(
|
text: String): Funit = $update(
|
||||||
$select(id),
|
$select(id),
|
||||||
$push("messages", WatcherRoom.encode(userId, text)),
|
$push("messages", WatcherRoom.encode(username, text)),
|
||||||
upsert = true)
|
upsert = true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ final class Env(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val messenger = new Messenger(NetDomain)
|
lazy val messenger = new Messenger(getUsername, NetDomain)
|
||||||
|
|
||||||
private[tournament] lazy val tournamentColl = db(CollectionTournament)
|
private[tournament] lazy val tournamentColl = db(CollectionTournament)
|
||||||
private[tournament] lazy val roomColl = db(CollectionRoom)
|
private[tournament] lazy val roomColl = db(CollectionRoom)
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
package lila.tournament
|
package lila.tournament
|
||||||
|
|
||||||
import lila.db.api.$find
|
import lila.db.api._
|
||||||
import tube.tournamentTube
|
import tube.tournamentTube
|
||||||
import lila.user.{ User, UserRepo, Room ⇒ UserRoom }
|
import lila.user.{ User, UserRepo, Room ⇒ UserRoom }
|
||||||
|
|
||||||
private[tournament] final class Messenger(val netDomain: String) extends UserRoom {
|
private[tournament] final class Messenger(
|
||||||
|
getUsername: String ⇒ Fu[Option[String]],
|
||||||
|
val netDomain: String) extends UserRoom {
|
||||||
|
|
||||||
import Room._
|
import Room._
|
||||||
|
|
||||||
def init(tour: Created): Fu[List[Message]] = for {
|
def init(tour: Created): Fu[List[Message]] = for {
|
||||||
userOption ← UserRepo named tour.data.createdBy
|
username ← getUsername(tour.data.createdBy) flatMap {
|
||||||
username = userOption.fold(tour.data.createdBy)(_.username)
|
_.fold[Fu[String]](fufail("No username found"))(fuccess(_))
|
||||||
|
}
|
||||||
message ← systemMessage(tour, "%s creates the tournament" format username)
|
message ← systemMessage(tour, "%s creates the tournament" format username)
|
||||||
} yield List(message)
|
} yield List(message)
|
||||||
|
|
||||||
def userMessage(tournamentId: String, userId: String, text: String): Fu[Message] = for {
|
def userMessage(tournamentId: String, userId: String, text: String): Fu[Message] = for {
|
||||||
userOption ← UserRepo named userId
|
userOption ← UserRepo byId userId
|
||||||
tourOption ← $find byId tournamentId
|
tourExists ← $count.exists($select(tournamentId))
|
||||||
message ← (for {
|
message ← (for {
|
||||||
user ← userOption filter (_.canChat) toValid "This user cannot chat"
|
_ ← Unit.validIf(tourExists, "No such tournament")
|
||||||
_ ← tourOption toValid "No such tournament"
|
msg ← userMessage(userOption, text)
|
||||||
msg ← createMessage(user, text)
|
|
||||||
(u, t) = msg
|
(u, t) = msg
|
||||||
} yield Message(u.some, t)).future
|
} yield Message(u.some, t)).future
|
||||||
_ ← RoomRepo.addMessage(tournamentId, message)
|
_ ← RoomRepo.addMessage(tournamentId, message)
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
package lila.user
|
package lila.user
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
|
||||||
import java.util.regex.Matcher.quoteReplacement
|
import java.util.regex.Matcher.quoteReplacement
|
||||||
|
|
||||||
trait Room {
|
trait Room {
|
||||||
|
|
||||||
def netDomain: String
|
def netDomain: String
|
||||||
|
|
||||||
def createMessage(user: User, text: String): Valid[(String, String)] =
|
def userMessage(userOption: Option[User], text: String): Valid[(String, String)] =
|
||||||
if (user.isChatBan) !!("Chat banned " + user)
|
userOption toValid "Anonymous cannot talk in this room" flatMap { user ⇒
|
||||||
else if (user.disabled) !!("User disabled " + user)
|
if (user.isChatBan) !!("Chat banned " + user)
|
||||||
else escapeXml(text.replace(""""""", "'").trim take 140) |> { escaped ⇒
|
else if (user.disabled) !!("User disabled " + user)
|
||||||
(escaped.nonEmpty).fold(
|
else cleanupText(text) map { user.username -> _ }
|
||||||
success((
|
|
||||||
user.username,
|
|
||||||
urlRegex.replaceAllIn(escaped, m ⇒ quoteReplacement(netDomain + "/" + (m group 1)))
|
|
||||||
)),
|
|
||||||
!!("Empty message")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def userOrAnonMessage(userOption: Option[User], text: String): Valid[(Option[String], String)] =
|
||||||
|
cleanupText(text) map { userOption.map(_.username) -> _ }
|
||||||
|
|
||||||
|
def cleanupText(text: String): Valid[String] =
|
||||||
|
(text.replace(""""""", "'").trim take 140) |> { t ⇒
|
||||||
|
if (t.isEmpty) !!("Empty message")
|
||||||
|
else success(delocalize(noPrivateUrl(t)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def noPrivateUrl(str: String): String =
|
||||||
|
urlRegex.replaceAllIn(str, m ⇒ quoteReplacement(netDomain + "/" + (m group 1)))
|
||||||
|
|
||||||
|
private val delocalize = new lila.common.String.Delocalizer(netDomain)
|
||||||
private val domainRegex = netDomain.replace(".", """\.""")
|
private val domainRegex = netDomain.replace(".", """\.""")
|
||||||
private val urlRegex = (domainRegex + """/([\w-]{8})[\w-]{4}""").r
|
private val urlRegex = (domainRegex + """/([\w-]{8})[\w-]{4}""").r
|
||||||
|
|
||||||
private def cleanupText(text: String) = {
|
|
||||||
val cleanedUp = text.trim.replace(""""""", "'")
|
|
||||||
(cleanedUp.size <= 140 && cleanedUp.nonEmpty) option cleanedUp
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ object UserRepo {
|
||||||
|
|
||||||
val normalize = User normalize _
|
val normalize = User normalize _
|
||||||
|
|
||||||
|
def byId(id: ID): Fu[Option[User]] = $find byId id
|
||||||
|
|
||||||
def named(username: String): Fu[Option[User]] = $find byId normalize(username)
|
def named(username: String): Fu[Option[User]] = $find byId normalize(username)
|
||||||
|
|
||||||
def nameds(usernames: List[String]): Fu[List[User]] = $find byIds usernames.map(normalize)
|
def nameds(usernames: List[String]): Fu[List[User]] = $find byIds usernames.map(normalize)
|
||||||
|
|
|
@ -203,6 +203,10 @@ var lichess_translations = [];
|
||||||
domain: document.domain.replace(/^\w+\.(.+)$/, '$1')
|
domain: document.domain.replace(/^\w+\.(.+)$/, '$1')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.userLink = function(u, limit) {
|
||||||
|
return (u || false) ? '<a class="user_link" href="/@/' + u + '">' + u + '</a>' : 'Anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
var lichess = {
|
var lichess = {
|
||||||
socket: null,
|
socket: null,
|
||||||
socketDefaults: {
|
socketDefaults: {
|
||||||
|
@ -587,7 +591,14 @@ var lichess_translations = [];
|
||||||
self.initTable();
|
self.initTable();
|
||||||
self.initClocks();
|
self.initClocks();
|
||||||
if (self.$chat) self.$chat.chat({
|
if (self.$chat) self.$chat.chat({
|
||||||
resize: true
|
resize: true,
|
||||||
|
render: function(u, t) {
|
||||||
|
if (self.options.player.spectator) {
|
||||||
|
return '<li><span>' + $.userLink(u, 12) + '</span>' + urlToLink(t) + '</li>';
|
||||||
|
} else {
|
||||||
|
return '<li class="' + u + (u == 'system' ? ' trans_me' : '') + '">' + urlToLink(t) + '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
self.$watchers.watchers();
|
self.$watchers.watchers();
|
||||||
if (self.isMyTurn() && self.options.game.turns == 0) {
|
if (self.isMyTurn() && self.options.game.turns == 0) {
|
||||||
|
@ -638,9 +649,9 @@ var lichess_translations = [];
|
||||||
ack: function() {
|
ack: function() {
|
||||||
clearTimeout(self.socketAckTimeout);
|
clearTimeout(self.socketAckTimeout);
|
||||||
},
|
},
|
||||||
message: function(event) {
|
message: function(e) {
|
||||||
self.element.queue(function() {
|
self.element.queue(function() {
|
||||||
if (self.$chat) self.$chat.chat("append", event);
|
if (self.$chat) self.$chat.chat("append", e.u, e.t);
|
||||||
self.element.dequeue();
|
self.element.dequeue();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1182,18 +1193,7 @@ var lichess_translations = [];
|
||||||
set: function(users) {
|
set: function(users) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
var html = [],
|
self.list.html(_.map(users, $.userLink).join(", "));
|
||||||
user, i, w;
|
|
||||||
for (i in users) {
|
|
||||||
w = users[i];
|
|
||||||
if (w.indexOf("Anonymous") == 0) {
|
|
||||||
user = w;
|
|
||||||
} else {
|
|
||||||
user = '<a href="/@/' + w + '">' + w + '</a>';
|
|
||||||
}
|
|
||||||
html.push(user);
|
|
||||||
}
|
|
||||||
self.list.html(html.join(", "));
|
|
||||||
self.element.show();
|
self.element.show();
|
||||||
} else {
|
} else {
|
||||||
self.element.hide();
|
self.element.hide();
|
||||||
|
@ -1204,8 +1204,8 @@ var lichess_translations = [];
|
||||||
$.widget("lichess.chat", {
|
$.widget("lichess.chat", {
|
||||||
_create: function() {
|
_create: function() {
|
||||||
this.options = $.extend({
|
this.options = $.extend({
|
||||||
render: function(t) {
|
render: function(u, t) {
|
||||||
return urlToLink(t);
|
console.debug("How to render " + u + " " + t + " in chat?");
|
||||||
},
|
},
|
||||||
resize: false
|
resize: false
|
||||||
}, this.options);
|
}, this.options);
|
||||||
|
@ -1252,15 +1252,14 @@ var lichess_translations = [];
|
||||||
}
|
}
|
||||||
$toggle[0].checked = $toggle.data("enabled");
|
$toggle[0].checked = $toggle.data("enabled");
|
||||||
},
|
},
|
||||||
append: function(msg, u) {
|
append: function(u, t) {
|
||||||
this._appendHtml(this.options.render(msg, u));
|
this._appendHtml(this.options.render(u, t));
|
||||||
},
|
},
|
||||||
appendMany: function(objs) {
|
appendMany: function(objs) {
|
||||||
var self = this,
|
var self = this,
|
||||||
html = "";
|
html = "";
|
||||||
$.each(objs, function() {
|
$.each(objs, function() {
|
||||||
if (this.txt) html += self.options.render(this.txt, this.u);
|
html += self.options.render(this.u, this.t);
|
||||||
else if (this.msg) html += self.options.render(this.msg, this.u);
|
|
||||||
});
|
});
|
||||||
this._appendHtml(html);
|
this._appendHtml(html);
|
||||||
},
|
},
|
||||||
|
@ -1728,10 +1727,8 @@ var lichess_translations = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
var $chat = $("div.lichess_chat").chat({
|
var $chat = $("div.lichess_chat").chat({
|
||||||
render: function(txt, u) {
|
render: function(u, t) {
|
||||||
return (u || false)
|
return (u || false) ? '<li><span><a class="user_link" href="/@/' + u + '">' + u.substr(0, 12) + '</a></span>' + urlToLink(t) + '</li>' : '<li>' + urlToLink(t) + '</li>';
|
||||||
? '<li><span><a class="user_link" href="/@/' + u + '">' + u.substr(0, 12) + '</a></span>' + urlToLink(txt) + '</li>'
|
|
||||||
: '<li>' + urlToLink(txt) + '</li>';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1757,7 +1754,7 @@ var lichess_translations = [];
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
talk: function(e) {
|
talk: function(e) {
|
||||||
$chat.chat('append', e.txt, e.u);
|
$chat.chat('append', e.u, e.t);
|
||||||
},
|
},
|
||||||
untalk: function(e) {
|
untalk: function(e) {
|
||||||
$chat.chat('remove', e.regex);
|
$chat.chat('remove', e.regex);
|
||||||
|
@ -1927,8 +1924,8 @@ var lichess_translations = [];
|
||||||
var $watchers = $("div.watchers").watchers();
|
var $watchers = $("div.watchers").watchers();
|
||||||
|
|
||||||
var $chat = $("div.lichess_chat").chat({
|
var $chat = $("div.lichess_chat").chat({
|
||||||
render: function(txt, u) {
|
render: function(u, t) {
|
||||||
return '<li><span><a class="user_link" href="/@/' + u + '">' + u.substr(0, 12) + '</a></span>' + urlToLink(txt) + '</li>';
|
return '<li><span>' + userLink(u, 12) + '</span>' + urlToLink(t) + '</li>';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1956,7 +1953,7 @@ var lichess_translations = [];
|
||||||
lichess.socket = new strongSocket(lichess.socketUrl + socketUrl, _ld_.version, $.extend(true, lichess.socketDefaults, {
|
lichess.socket = new strongSocket(lichess.socketUrl + socketUrl, _ld_.version, $.extend(true, lichess.socketDefaults, {
|
||||||
events: {
|
events: {
|
||||||
talk: function(e) {
|
talk: function(e) {
|
||||||
$chat.chat('append', e.txt, e.u);
|
$chat.chat('append', e.u, e.t);
|
||||||
},
|
},
|
||||||
start: start,
|
start: start,
|
||||||
reload: reload,
|
reload: reload,
|
||||||
|
@ -2004,8 +2001,8 @@ var lichess_translations = [];
|
||||||
ignoreUnknownMessages: true
|
ignoreUnknownMessages: true
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
message: function(event) {
|
message: function(e) {
|
||||||
$chat.chat("append", event);
|
$chat.chat("append", e.u, e.t);
|
||||||
},
|
},
|
||||||
crowd: function(event) {
|
crowd: function(event) {
|
||||||
$watchers.watchers("set", event.watchers);
|
$watchers.watchers("set", event.watchers);
|
||||||
|
|
1
todo
1
todo
|
@ -82,6 +82,7 @@ stream game export
|
||||||
show fen only after game is finished http://en.lichess.org/forum/lichess-feedback/please-disable-live-fen-notation?page=1
|
show fen only after game is finished http://en.lichess.org/forum/lichess-feedback/please-disable-live-fen-notation?page=1
|
||||||
I owe the admins decent tools
|
I owe the admins decent tools
|
||||||
move db game.if (initialFen) to another collection to prevent systematic loading, and doc moving
|
move db game.if (initialFen) to another collection to prevent systematic loading, and doc moving
|
||||||
|
tell opponent chat is disabled
|
||||||
|
|
||||||
DEPLOY p21
|
DEPLOY p21
|
||||||
----------
|
----------
|
||||||
|
|
Loading…
Reference in a new issue