show patron flair in chats - for lichess-org/strategy#90

pull/9050/head
Thibault Duplessis 2021-05-28 10:52:10 +02:00
parent 5709fb4312
commit 9d42eebcb0
10 changed files with 62 additions and 20 deletions

View File

@ -97,7 +97,7 @@ final class GameStateStream(
def receive = {
case MoveGameEvent(g, _, _) if g.id == id && !g.finished => pushState(g).unit
case lila.chat.actorApi.ChatLine(chatId, UserLine(username, _, text, false, false)) =>
case lila.chat.actorApi.ChatLine(chatId, UserLine(username, _, _, text, false, false)) =>
pushChatLine(username, text, chatId.value.lengthIs == Game.gameIdSize).unit
case FinishGame(g, _, _) if g.id == id => onGameOver(g.some).unit
case AbortedBy(pov) if pov.gameId == id => onGameOver(pov.game.some).unit

View File

@ -122,7 +122,7 @@ final class ChatApi(
def clear(chatId: Chat.Id) = coll.delete.one($id(chatId)).void
def system(chatId: Chat.Id, text: String, busChan: BusChan.Select): Funit = {
val line = UserLine(systemUserId, None, text, troll = false, deleted = false)
val line = UserLine(systemUserId, None, false, text, troll = false, deleted = false)
persistLine(chatId, line) >>- {
cached.invalidate(chatId)
publish(chatId, actorApi.ChatLine(chatId, line), busChan)
@ -131,7 +131,7 @@ final class ChatApi(
// like system, but not persisted.
def volatile(chatId: Chat.Id, text: String, busChan: BusChan.Select): Unit = {
val line = UserLine(systemUserId, None, text, troll = false, deleted = false)
val line = UserLine(systemUserId, None, false, text, troll = false, deleted = false)
publish(chatId, actorApi.ChatLine(chatId, line), busChan)
}
@ -191,6 +191,7 @@ final class ChatApi(
val line = c.hasRecentLine(user) option UserLine(
username = systemUserId,
title = None,
patron = user.isPatron,
text = lineText,
troll = false,
deleted = false
@ -249,6 +250,7 @@ final class ChatApi(
UserLine(
user.username,
user.title,
user.isPatron,
Writer.removeSelfMention(Writer preprocessUserInput t2, user.username),
troll = user.isTroll,
deleted = false

View File

@ -70,6 +70,7 @@ object JsonView {
)
.add("r" -> l.troll)
.add("d" -> l.deleted)
.add("p" -> l.patron)
.add("title" -> l.title)
}

View File

@ -18,6 +18,7 @@ sealed trait Line {
case class UserLine(
username: String,
title: Option[Title],
patron: Boolean,
text: String,
troll: Boolean,
deleted: Boolean
@ -52,7 +53,7 @@ object Line {
import reactivemongo.api.bson._
private val invalidLine = UserLine("", None, "[invalid character]", troll = false, deleted = true)
private val invalidLine = UserLine("", None, false, "[invalid character]", troll = false, deleted = true)
implicit private[chat] val userLineBSONHandler = BSONStringHandler.as[UserLine](
v => strToUserLine(v) getOrElse invalidLine,
@ -64,23 +65,30 @@ object Line {
lineToStr
)
private val UserLineRegex = """(?s)([\w-~]{2,}+)([ !?])(.++)""".r
private val trollChar = "!"
private val deletedChar = "?"
private val patronChar = "&"
private val UserLineRegex = {
"""(?s)([\w-~]{2,}+)([ """ + s"$trollChar$deletedChar$patronChar" + """])(.++)"""
}.r
private def strToUserLine(str: String): Option[UserLine] =
str match {
case UserLineRegex(username, sep, text) =>
val troll = sep == "!"
val deleted = sep == "?"
val troll = sep == trollChar
val deleted = sep == deletedChar
val patron = sep == patronChar
username split titleSep match {
case Array(title, name) =>
UserLine(name, Title get title, text, troll = troll, deleted = deleted).some
case _ => UserLine(username, None, text, troll = troll, deleted = deleted).some
UserLine(name, Title get title, patron, text, troll = troll, deleted = deleted).some
case _ => UserLine(username, None, patron, text, troll = troll, deleted = deleted).some
}
case _ => none
}
def userLineToStr(x: UserLine): String = {
val sep =
if (x.troll) "!"
else if (x.deleted) "?"
if (x.troll) trollChar
else if (x.deleted) deletedChar
else if (x.patron) patronChar
else " "
val tit = x.title.??(_.value + titleSep)
s"$tit${x.username}$sep${x.text}"

View File

@ -110,7 +110,7 @@ case class User(
def isPatron = plan.active
def activePlan: Option[Plan] = if (plan.active) Some(plan) else None
def activePlan: Option[Plan] = plan.active option plan
def planMonths: Option[Int] = activePlan.map(_.months)
@ -188,9 +188,16 @@ object User {
case class TotpToken(value: String) extends AnyVal
case class PasswordAndToken(password: ClearPassword, token: Option[TotpToken])
case class Speaker(username: String, title: Option[Title], enabled: Boolean, marks: Option[UserMarks]) {
def isBot = title has Title.BOT
def isTroll = marks.exists(_.troll)
case class Speaker(
username: String,
title: Option[Title],
enabled: Boolean,
plan: Option[Plan],
marks: Option[UserMarks]
) {
def isBot = title has Title.BOT
def isTroll = marks.exists(_.troll)
def isPatron = plan.exists(_.active)
}
case class Contact(
@ -279,6 +286,8 @@ object User {
import lila.db.BSON
import lila.db.dsl._
implicit private def planHandler = Plan.planBSONHandler
implicit val userBSONHandler = new BSON[User] {
import BSONFields._
@ -288,7 +297,6 @@ object User {
implicit private def countHandler = Count.countBSONHandler
implicit private def profileHandler = Profile.profileBSONHandler
implicit private def perfsHandler = Perfs.perfsBSONHandler
implicit private def planHandler = Plan.planBSONHandler
implicit private def totpSecretHandler = TotpSecret.totpSecretBSONHandler
def reads(r: BSON.Reader): User = {

View File

@ -644,9 +644,17 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
(res.nModified == 1) ?? email(id)
}
private val speakerProjection = $doc(
F.username -> true,
F.title -> true,
F.plan -> true,
F.enabled -> true,
F.marks -> true
)
def speaker(id: User.ID): Fu[Option[User.Speaker]] = {
import User.speakerHandler
coll.one[User.Speaker]($id(id))
coll.one[User.Speaker]($id(id), speakerProjection)
}
def contacts(orig: User.ID, dest: User.ID): Fu[Option[User.Contacts]] = {

View File

@ -44,6 +44,13 @@
margin-right: 0.4em;
}
line.patron {
font-size: 1.1em;
width: 1.2em;
text-align: left;
color: $c-brag;
}
.utitle {
margin-right: 3px;
}

View File

@ -208,7 +208,7 @@ function renderLine(ctrl: Ctrl, line: Line): VNode {
if (line.c) return h('li', [h('span.color', '[' + line.c + ']'), textNode]);
const userNode = thunk('a', line.u, userLink, [line.u, line.title]);
const userNode = thunk('a', line.u, userLink, [line.u, line.title, line.p]);
return h(
'li',

View File

@ -47,6 +47,7 @@ export interface Line {
d: boolean; // deleted
c?: string; // color
r?: boolean; // troll
p?: boolean; // patron
title?: string;
}

View File

@ -1,7 +1,14 @@
import { h, VNode } from 'snabbdom';
export function userLink(u: string, title?: string): VNode {
export function userLink(u: string, title?: string, patron?: boolean): VNode {
const trunc = u.substring(0, 14);
const line = patron
? h('line.line.patron', {
attrs: {
title: 'Lichess Patron',
},
})
: undefined;
return h(
'a',
{
@ -14,7 +21,7 @@ export function userLink(u: string, title?: string): VNode {
href: '/@/' + u,
},
},
title && title != 'BOT' ? [h('span.utitle', title), trunc] : [trunc]
title && title != 'BOT' ? [line, h('span.utitle', title), trunc] : [line, trunc]
);
}