automated chan eviction
This commit is contained in:
parent
33b900d3ca
commit
a20f292810
|
@ -185,13 +185,11 @@ private[controllers] trait LilaController
|
|||
private def contextBuilder(ctx: lila.user.UserContext, withChan: Option[Chan] = None): Fu[(Option[Chat], Option[lila.pref.Pref], Option[JsObject])] =
|
||||
ctx.me.fold(fuccess((none[Chat], none[lila.pref.Pref], none[JsObject]))) { me ⇒
|
||||
(HTTPRequest.isSynchronousHttp(ctx.req) ?? {
|
||||
Env.chat.api.get(me) flatMap { chan ⇒
|
||||
Env.chat.api.populate(withChan.fold(chan)(chan.withPageChan), me) recoverWith {
|
||||
case e: Exception ⇒ {
|
||||
play.api.Logger("controller").info(e.getMessage)
|
||||
Env.chat.api.populate(chan, me)
|
||||
}
|
||||
Env.chat.api get me flatMap { chat ⇒
|
||||
val pageChat = withChan.fold(chat) { chan ⇒
|
||||
Env.chat.api.truncate(me, chat withPageChan chan, chan.key)
|
||||
}
|
||||
Env.chat.api.populate(pageChat, me)
|
||||
} map (_.some)
|
||||
} recover {
|
||||
case e: Exception ⇒ {
|
||||
|
|
|
@ -2,48 +2,61 @@ package lila.chat
|
|||
|
||||
import java.util.regex.Matcher.quoteReplacement
|
||||
|
||||
import Line.{ BSONFields ⇒ L }
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||
import play.api.i18n.Lang
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.db.api._
|
||||
import lila.db.Implicits._
|
||||
import lila.user.{ User, UserRepo }
|
||||
import tube.lineTube
|
||||
|
||||
private[chat] final class Api(
|
||||
namer: Namer,
|
||||
chanVoter: ChanVoter,
|
||||
flood: lila.security.Flood,
|
||||
relationApi: lila.relation.RelationApi,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
netDomain: String) {
|
||||
|
||||
def get(user: User): Fu[ChatHead] = prefApi getPref user flatMap { p ⇒
|
||||
val langChan = LangChan(Lang(user.lang | "en"))
|
||||
p.chat.isDefault.fold({
|
||||
val p2 = p.updateChat(c ⇒ ChatHead(c) join langChan updatePref c)
|
||||
prefApi setPref p2 inject p2.chat
|
||||
}, fuccess(p.chat)) map { pref ⇒
|
||||
ChatHead(pref).setChan(langChan, true)
|
||||
}
|
||||
def join(user: User, chat: ChatHead, chan: Chan): ChatHead = {
|
||||
chanVoter(user.id, chan.key)
|
||||
truncate(user, chat join chan, chan.key)
|
||||
}
|
||||
|
||||
def get(userId: String): Fu[ChatHead] =
|
||||
(UserRepo byId userId) flatten s"No such user: $userId" flatMap get
|
||||
def join(member: ChatMember, chan: Chan): Fu[ChatHead] =
|
||||
getUser(member.userId) map { user ⇒ join(user, member.head, chan) }
|
||||
|
||||
def show(user: User, chat: ChatHead, chan: Chan): ChatHead =
|
||||
truncate(user, chat.setChan(chan, true), chan.key)
|
||||
|
||||
def truncate(user: User, chat: ChatHead, not: String): ChatHead =
|
||||
if (chat.chans.size <= Chat.maxChans) chat else {
|
||||
val nots = Set(LangChan(user).key, not)
|
||||
def filter(keys: Seq[String]) = keys filterNot nots.contains
|
||||
filter(chat.inactiveChanKeys).headOption match {
|
||||
case Some(key) ⇒ truncate(user, chat.unsetChanKey(key), not)
|
||||
case _ ⇒ chanVoter.lessVoted(user.id, filter(chat.chanKeys)) match {
|
||||
case Some(key) ⇒ truncate(user, chat.unsetChanKey(key), not)
|
||||
case _ ⇒ filter(chat.chanKeys).headOption match {
|
||||
case Some(key) ⇒ truncate(user, chat unsetChanKey key, not)
|
||||
case _ ⇒ chat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def get(user: User): Fu[ChatHead] = prefApi getPref user map { pref ⇒
|
||||
show(user, ChatHead(pref.chat), LangChan(user))
|
||||
}
|
||||
|
||||
def get(userId: String): Fu[ChatHead] = getUser(userId) flatMap get
|
||||
|
||||
def populate(head: ChatHead, user: User): Fu[Chat] =
|
||||
relationApi blocking user.id flatMap { blocks ⇒
|
||||
val selectTroll = user.troll.fold(Json.obj(), Json.obj(L.troll -> false))
|
||||
val selectBlock = Json.obj(L.username -> $nin(blocks))
|
||||
namer.chans(head.chans, user) zip
|
||||
$find($query(
|
||||
selectTroll ++
|
||||
selectBlock ++
|
||||
Json.obj(L.chan -> $in(head.activeChanKeys))
|
||||
) sort $sort.desc(L.date), 20) map {
|
||||
case (namedChans, lines) ⇒ Chat(head, namedChans, lines.reverse)
|
||||
namer.chans(head.chans, user) zip {
|
||||
relationApi blocking user.id flatMap {
|
||||
LineRepo.find(head.activeChanKeys, user.troll, _, 20) flatMap {
|
||||
_.map(namer.line).sequenceFu
|
||||
}
|
||||
}
|
||||
} map {
|
||||
case (namedChans, namedLines) ⇒ Chat(head, namedChans, namedLines.reverse)
|
||||
}
|
||||
|
||||
def makeLine(chanName: String, userId: String, t1: String): Fu[Option[Line]] =
|
||||
|
@ -67,8 +80,7 @@ private[chat] final class Api(
|
|||
case UserChan(u1, u2) ⇒ relationApi.areFriends(u1, u2)
|
||||
case _ ⇒ fuccess(true)
|
||||
}) flatMap {
|
||||
case true ⇒ write(line) inject line.some
|
||||
case false ⇒ fuccess(none)
|
||||
_ ?? (LineRepo insert line inject line.some)
|
||||
}
|
||||
case Some(line) ⇒ {
|
||||
logger.info(s"Flood: $userId @ $chanName : $text")
|
||||
|
@ -76,13 +88,14 @@ private[chat] final class Api(
|
|||
}
|
||||
}
|
||||
|
||||
def write(line: Line): Funit = $insert bson line
|
||||
|
||||
def systemWrite(chan: Chan, text: String): Fu[Line] = {
|
||||
val line = Line.system(chan, text)
|
||||
$insert bson line inject line
|
||||
LineRepo insert line inject line
|
||||
}
|
||||
|
||||
private[chat] def getUser(userId: String) =
|
||||
(UserRepo byId userId) flatten s"No such user: $userId"
|
||||
|
||||
private val logger = play.api.Logger("chat")
|
||||
|
||||
private object Writer {
|
||||
|
|
|
@ -4,6 +4,7 @@ import play.api.i18n.Lang
|
|||
import play.api.libs.json._
|
||||
|
||||
import lila.i18n.LangList
|
||||
import lila.user.User
|
||||
|
||||
sealed trait Chan extends Ordered[Chan] {
|
||||
def typ: String
|
||||
|
@ -36,20 +37,24 @@ sealed abstract class IdChan(
|
|||
def idOption = id.some
|
||||
|
||||
def compare(other: Chan) = other match {
|
||||
case c: LangChan ⇒ -c.compare(this)
|
||||
case c: IdChan ⇒ id compare c.id
|
||||
case _ ⇒ -1
|
||||
case c: LangChan ⇒ 1
|
||||
case c: ContextualChan => -1
|
||||
case c: IdChan ⇒ typ compare c.typ match {
|
||||
case 0 ⇒ id compare c.id
|
||||
case x ⇒ x
|
||||
}
|
||||
case _ ⇒ -1
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait ContextualChan { self: Chan ⇒
|
||||
override val contextual = true
|
||||
override def compare(other: Chan) = 1
|
||||
}
|
||||
|
||||
sealed abstract class AutoActiveChan(typ: String, i: String)
|
||||
extends IdChan(typ, true) with ContextualChan {
|
||||
val id = i
|
||||
override def compare(other: Chan) = 1
|
||||
}
|
||||
|
||||
object TvChan extends StaticChan(Chan.typ.tv, "TV") with ContextualChan
|
||||
|
@ -67,6 +72,7 @@ case class LangChan(lang: Lang) extends IdChan(Chan.typ.lang, false) {
|
|||
}
|
||||
object LangChan {
|
||||
def apply(code: String): Option[LangChan] = LangList.exists(code) option LangChan(Lang(code))
|
||||
def apply(user: User): LangChan = LangChan(Lang(user.lang | "en"))
|
||||
}
|
||||
|
||||
case class UserChan(u1: String, u2: String) extends IdChan("user", false) {
|
||||
|
|
28
modules/chat/src/main/ChanVoter.scala
Normal file
28
modules/chat/src/main/ChanVoter.scala
Normal file
|
@ -0,0 +1,28 @@
|
|||
package lila.chat
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
private[chat] final class ChanVoter {
|
||||
|
||||
private type UserId = String
|
||||
private type ChanKey = String
|
||||
|
||||
private val cache = lila.memo.Builder.expiry[UserId, List[ChanKey]](1.hour)
|
||||
|
||||
def apply(userId: UserId, chanKey: ChanKey) {
|
||||
cache.put(userId, chanKey :: votedKeysOf(userId).filterNot(chanKey==))
|
||||
}
|
||||
|
||||
def lessVoted(userId: UserId, in: Seq[ChanKey]): Option[ChanKey] = {
|
||||
val votedKeys = votedKeysOf(userId)
|
||||
(in map { key ⇒
|
||||
key -> (votedKeys indexOf key match {
|
||||
case -1 ⇒ Int.MaxValue
|
||||
case x ⇒ x
|
||||
})
|
||||
} sortBy (-_._2)).headOption.map(_._1)
|
||||
}
|
||||
|
||||
private def votedKeysOf(userId: UserId): List[ChanKey] =
|
||||
~Option(cache getIfPresent userId)
|
||||
}
|
|
@ -18,9 +18,12 @@ case class ChatHead(
|
|||
def setChan(c: Chan, value: Boolean) = if (value) {
|
||||
if (chans contains c) this else copy(chans = c :: chans).sorted
|
||||
}
|
||||
else {
|
||||
if (chans contains c) copy(chans = chans filterNot (c==)) else this
|
||||
}
|
||||
else unsetChanKey(c.key)
|
||||
|
||||
def unsetChanKey(key: String) = copy(
|
||||
chans = chans filterNot (_.key == key),
|
||||
activeChanKeys = activeChanKeys filterNot (key==),
|
||||
mainChanKey = mainChanKey filterNot (key==))
|
||||
|
||||
def withPageChan(c: Chan) = setChan(c, true).copy(
|
||||
pageChanKey = c.key.some,
|
||||
|
@ -28,12 +31,16 @@ case class ChatHead(
|
|||
mainChanKey = c.autoActive.fold(c.key.some, mainChanKey))
|
||||
|
||||
def setActiveChanKey(key: String, value: Boolean) =
|
||||
copy(activeChanKeys = if (value) activeChanKeys + key else activeChanKeys - key)
|
||||
copy(activeChanKeys = if (value && exists(key)) activeChanKeys + key else activeChanKeys - key)
|
||||
|
||||
def exists(key: String) = chanKeys contains key
|
||||
|
||||
def setMainChanKey(key: Option[String]) = copy(mainChanKey = key)
|
||||
|
||||
def join(c: Chan) = setChan(c, true).setActiveChanKey(c.key, true).setMainChanKey(c.key.some)
|
||||
|
||||
def inactiveChanKeys = chanKeys filterNot activeChanKeys.contains
|
||||
|
||||
def updatePref(pref: ChatPref) = ChatPref(
|
||||
on = pref.on,
|
||||
chans = chans filterNot (_.contextual) map (_.key),
|
||||
|
@ -52,7 +59,7 @@ object ChatHead {
|
|||
mainChanKey = pref.mainChan).sorted
|
||||
}
|
||||
|
||||
case class Chat(head: ChatHead, namedChans: List[NamedChan], lines: List[Line]) {
|
||||
case class Chat(head: ChatHead, namedChans: List[NamedChan], lines: List[NamedLine]) {
|
||||
|
||||
def toJson = Json.obj(
|
||||
"lines" -> lines.map(_.toJson),
|
||||
|
@ -67,5 +74,5 @@ object Chat {
|
|||
|
||||
val maxChans = 7
|
||||
|
||||
val systemUsername = "Lichess"
|
||||
val systemUserId = "lichess"
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import lila.hub.actorApi.chat._
|
|||
import lila.pref.{ Pref, PrefApi }
|
||||
import lila.socket.actorApi.{ SocketEnter, SocketLeave }
|
||||
import lila.socket.Socket
|
||||
import lila.user.UserRepo
|
||||
|
||||
private[chat] final class ChatActor(
|
||||
api: Api,
|
||||
|
@ -34,12 +33,13 @@ private[chat] final class ChatActor(
|
|||
|
||||
def receive = {
|
||||
|
||||
case line: Line ⇒ {
|
||||
lazy val json = lineMessage(line)
|
||||
case line: Line ⇒ lineMessage(line) foreach { json ⇒
|
||||
members.values foreach { m ⇒ if (m wants line) m tell json }
|
||||
}
|
||||
|
||||
case Tell(uid, line) ⇒ members get uid foreach { _ tell lineMessage(line) }
|
||||
case Tell(uid, line) ⇒ lineMessage(line) foreach { json ⇒
|
||||
members get uid foreach { _ tell json }
|
||||
}
|
||||
|
||||
case System(chanTyp, chanId, text) ⇒ Chan(chanTyp, chanId) foreach { chan ⇒
|
||||
api.systemWrite(chan, text) pipeTo self
|
||||
|
@ -47,8 +47,8 @@ private[chat] final class ChatActor(
|
|||
|
||||
case SetOpen(member, value) ⇒ prefApi.setChatPref(member.userId, _.copy(on = value))
|
||||
|
||||
case Join(member, chan) ⇒ {
|
||||
member.updateHead(_ join chan)
|
||||
case Join(member, chan) ⇒ api.join(member, chan) foreach { head ⇒
|
||||
member setHead head
|
||||
saveAndReload(member)
|
||||
}
|
||||
|
||||
|
@ -58,11 +58,12 @@ private[chat] final class ChatActor(
|
|||
}
|
||||
|
||||
case Query(member, toId) ⇒
|
||||
UserRepo byId toId flatten s"Can't query non existing user $toId" foreach { to ⇒
|
||||
api getUser toId foreach { to ⇒
|
||||
relationApi.follows(to.id, member.userId) onSuccess {
|
||||
case true ⇒
|
||||
member.updateHead(_ join UserChan(member.userId, toId))
|
||||
case true ⇒ api.join(member, UserChan(member.userId, toId)) foreach { head ⇒
|
||||
member setHead head
|
||||
saveAndReload(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,14 +80,18 @@ private[chat] final class ChatActor(
|
|||
case Input(uid, o) ⇒ (o str "t") |@| (o obj "d") |@| (members get uid) apply {
|
||||
case (typ, data, member) ⇒ typ match {
|
||||
|
||||
case "chat.register" ⇒ relationApi blocking member.userId foreach { blocks ⇒
|
||||
member.setHead(ChatHead(
|
||||
chans = (~data.arrAs("chans")(_.asOpt[String]) map Chan.parse).flatten,
|
||||
pageChanKey = data str "pageChan",
|
||||
activeChanKeys = (~data.arrAs("activeChans")(_.asOpt[String])).toSet,
|
||||
mainChanKey = data str "mainChan"))
|
||||
member setBlocks blocks
|
||||
reload(member)
|
||||
case "chat.register" ⇒ api getUser member.userId foreach { user ⇒
|
||||
relationApi blocking user.id zip (api get user) foreach {
|
||||
case (blocks, head) ⇒ {
|
||||
val pageHead = (data str "pageChan" flatMap Chan.parse).fold(head) {
|
||||
case c if c.autoActive ⇒ api.join(user, head, c)
|
||||
case c ⇒ api.show(user, head, c)
|
||||
}
|
||||
member setHead pageHead
|
||||
member setBlocks blocks
|
||||
reload(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "chat.tell" ⇒ data str "text" foreach { text ⇒
|
||||
val chanOption = data str "chan" flatMap Chan.parse
|
||||
|
@ -105,7 +110,9 @@ private[chat] final class ChatActor(
|
|||
case SocketLeave(uid) ⇒ members -= uid
|
||||
}
|
||||
|
||||
private def lineMessage(line: Line) = Socket.makeMessage("chat.line", line.toJson)
|
||||
private def lineMessage(line: Line) = namer line line map { namedLine ⇒
|
||||
Socket.makeMessage("chat.line", namedLine.toJson)
|
||||
}
|
||||
|
||||
private def withMembersOf(userId: String)(f: ChatMember ⇒ Unit) {
|
||||
members.values foreach { member ⇒
|
||||
|
@ -118,7 +125,8 @@ private[chat] final class ChatActor(
|
|||
}
|
||||
|
||||
private def reload(m: ChatMember) {
|
||||
UserRepo byId m.userId flatten s"User of $m not found" foreach { user ⇒
|
||||
Thread sleep 500
|
||||
api getUser m.userId foreach { user ⇒
|
||||
api.populate(m.head, user) foreach { chat ⇒
|
||||
m tell Socket.makeMessage("chat.reload", chat.toJson)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ final class Env(
|
|||
|
||||
lazy val api = new Api(
|
||||
namer = namer,
|
||||
chanVoter = new ChanVoter,
|
||||
flood = flood,
|
||||
relationApi = relationApi,
|
||||
prefApi = prefApi,
|
||||
|
|
|
@ -11,23 +11,24 @@ import lila.user.User
|
|||
case class Line(
|
||||
id: String,
|
||||
chan: Chan,
|
||||
username: String,
|
||||
userId: String,
|
||||
date: DateTime,
|
||||
text: String,
|
||||
troll: Boolean) {
|
||||
|
||||
def system = username == Chat.systemUsername
|
||||
def system = userId == Chat.systemUserId
|
||||
|
||||
def html = Html {
|
||||
escapeXml(text)
|
||||
}
|
||||
}
|
||||
|
||||
case class NamedLine(line: Line, username: String) {
|
||||
|
||||
def toJson = Json.obj(
|
||||
"chan" -> chan.key,
|
||||
"chan" -> line.chan.key,
|
||||
"user" -> username,
|
||||
"html" -> html.toString)
|
||||
|
||||
def userId = username.toLowerCase
|
||||
"html" -> line.html.toString)
|
||||
}
|
||||
|
||||
object Line {
|
||||
|
@ -37,7 +38,7 @@ object Line {
|
|||
def make(chan: Chan, user: User, text: String): Line = Line(
|
||||
id = Random nextString idSize,
|
||||
chan = chan,
|
||||
username = user.username,
|
||||
userId = user.id,
|
||||
date = DateTime.now,
|
||||
text = text,
|
||||
troll = user.troll)
|
||||
|
@ -45,7 +46,7 @@ object Line {
|
|||
def system(chan: Chan, text: String): Line = Line(
|
||||
id = Random nextString idSize,
|
||||
chan = chan,
|
||||
username = Chat.systemUsername,
|
||||
userId = Chat.systemUserId,
|
||||
date = DateTime.now,
|
||||
text = text,
|
||||
troll = false)
|
||||
|
@ -55,7 +56,7 @@ object Line {
|
|||
object BSONFields {
|
||||
val id = "_id"
|
||||
val chan = "c"
|
||||
val username = "u"
|
||||
val userId = "ui"
|
||||
val date = "d"
|
||||
val text = "t"
|
||||
val troll = "tr"
|
||||
|
@ -72,7 +73,7 @@ object Line {
|
|||
def reads(r: BSON.Reader): Line = Line(
|
||||
id = r str id,
|
||||
chan = Chan parse (r str chan) err s"Line has invalid chan: ${r.doc}",
|
||||
username = r str username,
|
||||
userId = r str userId,
|
||||
text = r str text,
|
||||
date = r date date,
|
||||
troll = r bool troll)
|
||||
|
@ -80,7 +81,7 @@ object Line {
|
|||
def writes(w: BSON.Writer, o: Line) = BSONDocument(
|
||||
id -> o.id,
|
||||
chan -> o.chan.key,
|
||||
username -> o.username,
|
||||
userId -> o.userId,
|
||||
text -> o.text,
|
||||
date -> o.date,
|
||||
troll -> o.troll)
|
||||
|
|
65
modules/chat/src/main/LineRepo.scala
Normal file
65
modules/chat/src/main/LineRepo.scala
Normal file
|
@ -0,0 +1,65 @@
|
|||
package lila.chat
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.json._
|
||||
import play.modules.reactivemongo.json.BSONFormats.toJSON
|
||||
import play.modules.reactivemongo.json.ImplicitBSONHandlers._
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.common.PimpedJson._
|
||||
import lila.db.api._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Implicits._
|
||||
import tube.lineTube
|
||||
|
||||
object LineRepo {
|
||||
|
||||
import Line.{ BSONFields ⇒ L }
|
||||
|
||||
def find(chanKeys: Set[String], troll: Boolean, blocks: Set[String], limit: Int): Fu[List[Line]] =
|
||||
$find($query(
|
||||
troll.fold(Json.obj(), Json.obj(L.troll -> false)) ++
|
||||
Json.obj(L.userId -> $nin(blocks)) ++
|
||||
Json.obj(L.chan -> $in(chanKeys))
|
||||
) sort $sort.desc(L.date), limit)
|
||||
|
||||
def insert(line: Line) = $insert bson line
|
||||
|
||||
def leastTalked(userId: String, chanKeys: Seq[String]): Fu[Option[String]] = {
|
||||
import reactivemongo.core.commands._
|
||||
val command = Aggregate(lineTube.coll.name, Seq(
|
||||
Match(JsObjectWriter write Json.obj(
|
||||
L.userId -> userId,
|
||||
L.chan -> $in(chanKeys),
|
||||
L.date -> $gt($date(DateTime.now.minusMinutes(30)))
|
||||
)),
|
||||
GroupField(L.chan)("nb" -> SumValue(1)),
|
||||
Sort(Seq(Ascending("nb"))),
|
||||
Limit(1)
|
||||
))
|
||||
lineTube.coll.db.command(command) map { stream ⇒
|
||||
stream.headOption flatMap { obj ⇒
|
||||
toJSON(obj).asOpt[JsObject] flatMap { _ str "_id" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def leastActive(chanKeys: Seq[String]): Fu[Option[String]] = {
|
||||
import reactivemongo.core.commands._
|
||||
val command = Aggregate(lineTube.coll.name, Seq(
|
||||
Match(JsObjectWriter write Json.obj(
|
||||
L.chan -> $in(chanKeys),
|
||||
L.date -> $gt($date(DateTime.now.minusMinutes(15)))
|
||||
)),
|
||||
GroupField(L.chan)("nb" -> SumValue(1)),
|
||||
Sort(Seq(Ascending("nb"))),
|
||||
Limit(1)
|
||||
))
|
||||
lineTube.coll.db.command(command) map { stream ⇒
|
||||
stream.headOption flatMap { obj ⇒
|
||||
toJSON(obj).asOpt[JsObject] flatMap { _ str "_id" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import lila.user.User
|
|||
|
||||
private[chat] final class Namer(getUsername: String ⇒ Fu[String]) {
|
||||
|
||||
def line(l: Line): Fu[NamedLine] = getUsername(l.userId) map { NamedLine(l, _) }
|
||||
|
||||
def chan(c: Chan, as: User): Fu[NamedChan] =
|
||||
chanCache(c -> as) map { NamedChan(c, _) }
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ object ApplicationBuild extends Build {
|
|||
)
|
||||
|
||||
lazy val chat = project("chat", Seq(
|
||||
common, db, hub, socket, user, security, pref, relation, game, tournament, i18n)).settings(
|
||||
common, db, hub, memo, socket, user, security, pref, relation, game, tournament, i18n)).settings(
|
||||
libraryDependencies ++= provided(
|
||||
play.api, RM, PRM)
|
||||
)
|
||||
|
|
|
@ -883,10 +883,7 @@ var storage = {
|
|||
|
||||
$('body').on('socket.open', function() {
|
||||
lichess.socket.send('chat.register', {
|
||||
chans: _.keys(self.head.chans),
|
||||
pageChan: self.head.pageChan,
|
||||
activeChans: self.head.activeChans,
|
||||
mainChan: self.head.mainChan
|
||||
pageChan: self.head.pageChan
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -511,8 +511,8 @@ div.footer div.right {
|
|||
#chat .chan {
|
||||
display: block;
|
||||
text-align: right;
|
||||
line-height: 31px;
|
||||
height: 31px;
|
||||
line-height: 27px;
|
||||
height: 27px;
|
||||
padding: 0px 10px;
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
cursor: pointer;
|
||||
|
@ -529,7 +529,7 @@ div.footer div.right {
|
|||
float: right;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 5px 16px 0 -13px;
|
||||
margin: 3px 16px 0 -13px;
|
||||
position: relative;
|
||||
}
|
||||
#chat div.check input[type=checkbox] {
|
||||
|
@ -578,9 +578,11 @@ div.footer div.right {
|
|||
#chat > .room > .lines {
|
||||
height: 171px;
|
||||
overflow: hidden;
|
||||
/* overflow-y: auto; */
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
#chat > .room > .lines:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#chat .line {
|
||||
width: 100%;
|
||||
padding: 2px 10px;
|
||||
|
|
5
todo
5
todo
|
@ -99,7 +99,7 @@ follow a user #chess claymore http://www.freechess.org/Help/HelpFiles/follow.htm
|
|||
team menu should lead to "My teams"
|
||||
atwar style chat system http://imgur.com/a/buwy3 (but better)
|
||||
replay games using move time
|
||||
antiboost tool for mods
|
||||
antifarming tool for mods
|
||||
list all players by ranking.
|
||||
automatic ai cheat detection is broken
|
||||
time pie chart colors http://en.lichess.org/52ede6hu/stats
|
||||
|
@ -109,4 +109,5 @@ full page recent forum posts
|
|||
|
||||
deploy
|
||||
------
|
||||
db.chat.ensureIndex({tr:1,c:1,d:-1}
|
||||
db.chat.ensureIndex({tr:1,c:1,d:-1})
|
||||
db.chat.ensureIndex({u:1,c:1,d:-1})
|
||||
|
|
Loading…
Reference in a new issue