Merge branch 'master' into trans-swiss
commit
09c705d242
|
@ -356,7 +356,10 @@ final class Account(
|
|||
|
||||
def apiKidPost =
|
||||
Scoped(_.Preference.Write) { req => me =>
|
||||
env.user.repo.setKid(me, getBool("v", req)) inject jsonOkResult
|
||||
getBoolOpt("v", req) match {
|
||||
case None => BadRequest(jsonError("Missing v parameter")).fuccess
|
||||
case Some(v) => env.user.repo.setKid(me, v) inject jsonOkResult
|
||||
}
|
||||
}
|
||||
|
||||
private def currentSessionId(implicit ctx: Context) =
|
||||
|
|
|
@ -202,7 +202,7 @@ final class Challenge(
|
|||
Action.async { req =>
|
||||
import cats.implicits._
|
||||
val scopes = List(OAuthScope.Challenge.Write)
|
||||
(get("token1", req) map AccessToken.Id, get("token2", req) map AccessToken.Id).mapN {
|
||||
(get("token1", req) map AccessToken.Id.apply, get("token2", req) map AccessToken.Id.apply).mapN {
|
||||
env.oAuth.server.authBoth(scopes)
|
||||
} ?? {
|
||||
_ flatMap {
|
||||
|
|
|
@ -20,6 +20,16 @@ final class Editor(env: Env) extends LilaController(env) {
|
|||
})
|
||||
}
|
||||
|
||||
private lazy val endgamePositionsJson = lila.common.String.html.safeJsonValue {
|
||||
JsArray(
|
||||
chess.EndgamePosition.positions map { p =>
|
||||
Json.obj(
|
||||
"name" -> p.name,
|
||||
"fen" -> p.fen
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
def index = load("")
|
||||
|
||||
def load(urlFen: String) =
|
||||
|
@ -35,7 +45,8 @@ final class Editor(env: Env) extends LilaController(env) {
|
|||
html.board.editor(
|
||||
sit = situation,
|
||||
fen = Forsyth >> situation,
|
||||
positionsJson
|
||||
positionsJson,
|
||||
endgamePositionsJson
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@ final class OAuth(env: Env) extends LilaController(env) {
|
|||
prompt.authorize(me) match {
|
||||
case Validated.Valid(authorized) =>
|
||||
env.oAuth.authorizationApi.create(authorized) map { code =>
|
||||
Redirect(authorized.redirectUrl(code))
|
||||
SeeOther(authorized.redirectUrl(code))
|
||||
}
|
||||
case Validated.Invalid(error) => Redirect(prompt.redirectUri.error(error, prompt.state)).fuccess
|
||||
case Validated.Invalid(error) => SeeOther(prompt.redirectUri.error(error, prompt.state)).fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package lila.app
|
|||
package templating
|
||||
|
||||
import play.api.data._
|
||||
import play.api.i18n.Lang
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
@ -26,6 +27,14 @@ trait FormHelper { self: I18nHelper =>
|
|||
val postForm = form(method := "post")
|
||||
val submitButton = button(tpe := "submit")
|
||||
|
||||
def markdownAvailable(implicit lang: Lang) =
|
||||
trans.markdownAvailable(
|
||||
a(
|
||||
href := "https://guides.github.com/features/mastering-markdown/",
|
||||
targetBlank
|
||||
)("Markdown")
|
||||
)
|
||||
|
||||
object form3 {
|
||||
|
||||
private val idPrefix = "form3"
|
||||
|
|
|
@ -150,6 +150,10 @@ object pref {
|
|||
setting(
|
||||
sayGgWpAfterLosingOrDrawing(),
|
||||
radios(form("behavior.courtesy"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
scrollOnTheBoardToReplayMoves(),
|
||||
radios(form("behavior.scrollMoves"), booleanChoices)
|
||||
)
|
||||
),
|
||||
categFieldset(PrefCateg.Privacy, categ)(
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package views.html.base
|
||||
|
||||
import com.github.blemale.scaffeine.LoadingCache
|
||||
import scala.concurrent.duration._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
object markdown {
|
||||
|
||||
private val renderer = new lila.common.Markdown
|
||||
|
||||
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterWrite(10 minutes)
|
||||
.maximumSize(256)
|
||||
.build(renderer.apply)
|
||||
|
||||
def apply(text: String): Frag = raw(cache get text)
|
||||
}
|
|
@ -59,6 +59,7 @@ object bits {
|
|||
trans.flipBoard,
|
||||
trans.loadPosition,
|
||||
trans.popularOpenings,
|
||||
trans.endgamePositions,
|
||||
trans.castling,
|
||||
trans.whiteCastlingKingside,
|
||||
trans.blackCastlingKingside,
|
||||
|
|
|
@ -13,7 +13,8 @@ object editor {
|
|||
def apply(
|
||||
sit: chess.Situation,
|
||||
fen: FEN,
|
||||
positionsJson: String
|
||||
positionsJson: String,
|
||||
endgamePositionsJson: String,
|
||||
)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.boardEditor.txt(),
|
||||
|
@ -21,7 +22,7 @@ object editor {
|
|||
jsModule("editor"),
|
||||
embedJsUnsafeLoadThen(
|
||||
s"""const data=${safeJsonValue(bits.jsData(sit, fen))};data.positions=$positionsJson;
|
||||
LichessEditor(document.getElementById('board-editor'), data);"""
|
||||
data.endgamePositions=$endgamePositionsJson;LichessEditor(document.getElementById('board-editor'), data);"""
|
||||
)
|
||||
),
|
||||
moreCss = cssTag("editor"),
|
||||
|
|
|
@ -27,7 +27,7 @@ object theirs {
|
|||
case Status.Created | Status.Offline =>
|
||||
frag(
|
||||
h1(
|
||||
if (c.isOpen) c.name | "Open Challenge"
|
||||
if (c.isOpen) c.name | "Open challenge"
|
||||
else
|
||||
user.fold[Frag]("Anonymous")(u =>
|
||||
frag(
|
||||
|
|
|
@ -34,16 +34,7 @@ object wall {
|
|||
ul(
|
||||
li(trans.clas.newsEdit2()),
|
||||
li(trans.clas.newsEdit3()),
|
||||
li(
|
||||
trans.clas.markdownAvailable(
|
||||
a(
|
||||
href := "https://guides.github.com/features/mastering-markdown/",
|
||||
targetBlank
|
||||
)(
|
||||
"Markdown"
|
||||
)
|
||||
)
|
||||
)
|
||||
li(markdownAvailable)
|
||||
)
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Clas.wallUpdate(c.id.value))(
|
||||
|
|
|
@ -75,8 +75,10 @@ object coordinate {
|
|||
),
|
||||
div(cls := "coord-trainer__board main-board")(
|
||||
svgTag(cls := "coords-svg", viewBoxAttr := "0 0 100 100")(
|
||||
textTag(cls := "coord current-coord"),
|
||||
textTag(cls := "coord next-coord")
|
||||
textTag(cls := "coord coord--resolved"),
|
||||
textTag(cls := "coord coord--current"),
|
||||
textTag(cls := "coord coord--next"),
|
||||
textTag(cls := "coord coord--new")
|
||||
),
|
||||
chessgroundBoard
|
||||
),
|
||||
|
|
|
@ -63,7 +63,7 @@ object event {
|
|||
)
|
||||
),
|
||||
e.description.map { d =>
|
||||
div(cls := "desc")(views.html.base.markdown(d))
|
||||
div(cls := "desc")(markdown(d))
|
||||
},
|
||||
if (e.isFinished) p(cls := "desc")("The event is finished.")
|
||||
else if (e.isNow) a(href := e.url, cls := "button button-fat")(trans.eventInProgress())
|
||||
|
@ -76,6 +76,18 @@ object event {
|
|||
)
|
||||
}
|
||||
|
||||
private object markdown {
|
||||
import scala.concurrent.duration._
|
||||
private val renderer = new lila.common.Markdown(table = true, list = true)
|
||||
private val cache: com.github.blemale.scaffeine.LoadingCache[String, String] =
|
||||
lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterAccess(10 minutes)
|
||||
.maximumSize(64)
|
||||
.build(renderer.apply)
|
||||
|
||||
def apply(text: String): Frag = raw(cache get text)
|
||||
}
|
||||
|
||||
def manager(events: List[Event])(implicit ctx: Context) = {
|
||||
val title = "Event manager"
|
||||
layout(title = title) {
|
||||
|
|
|
@ -139,7 +139,8 @@ object inquiry {
|
|||
)
|
||||
},
|
||||
isGranted(_.Shadowban) option
|
||||
a(href := routes.Mod.communicationPublic(in.user.id))("View", br, "Comms")
|
||||
a(href := routes.Mod.communicationPublic(in.user.id))("View", br, "Comms"),
|
||||
in.report.isAppeal option a(href := routes.Appeal.show(in.user.id))("View", br, "Appeal")
|
||||
),
|
||||
div(cls := "actions")(
|
||||
isGranted(_.ModMessage) option div(cls := "dropper warn buttons")(
|
||||
|
|
|
@ -71,6 +71,7 @@ object racer {
|
|||
s.raceComplete,
|
||||
s.spectating,
|
||||
s.joinTheRace,
|
||||
s.startTheRace,
|
||||
s.yourRankX,
|
||||
s.waitForRematch,
|
||||
s.nextRace,
|
||||
|
|
|
@ -55,7 +55,7 @@ object form {
|
|||
),
|
||||
postForm(cls := "terminate", action := routes.Simul.abort(simul.id))(
|
||||
submitButton(dataIcon := "", cls := "text button button-red confirm")(
|
||||
"Cancel the simul"
|
||||
trans.cancelSimul()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -94,11 +94,11 @@ object form {
|
|||
form3.split(
|
||||
form3.group(
|
||||
form("clockTime"),
|
||||
raw("Clock initial time"),
|
||||
trans.clockInitialTime(),
|
||||
help = trans.simulClockHint().some,
|
||||
half = true
|
||||
)(form3.select(_, clockTimeChoices)),
|
||||
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(
|
||||
form3.group(form("clockIncrement"), trans.clockIncrement(), half = true)(
|
||||
form3.select(_, clockIncrementChoices)
|
||||
)
|
||||
),
|
||||
|
@ -111,37 +111,38 @@ object form {
|
|||
)(
|
||||
form3.select(_, clockExtraChoices)
|
||||
),
|
||||
form3.group(form("color"), raw("Host color for each game"), half = true)(
|
||||
form3.group(form("color"), trans.simulHostcolor(), half = true)(
|
||||
form3.select(_, colorChoices)
|
||||
)
|
||||
),
|
||||
form3.split(
|
||||
teams.nonEmpty option
|
||||
form3.group(form("team"), raw("Only members of team"), half = true)(
|
||||
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
|
||||
form3.group(form("team"), trans.onlyMembersOfTeam(), half = true)(
|
||||
form3.select(_, List(("", trans.noRestriction.txt())) ::: teams.map(_.pair))
|
||||
),
|
||||
form3.group(
|
||||
form("position"),
|
||||
trans.startPosition(),
|
||||
klass = "position",
|
||||
half = true,
|
||||
help = views.html.tournament.form.positionInputHelp.some
|
||||
help =
|
||||
trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
|
||||
)(form3.input(_))
|
||||
),
|
||||
form3.group(
|
||||
form("estimatedStartAt"),
|
||||
frag("Estimated start time"),
|
||||
trans.estimatedStart(),
|
||||
half = true
|
||||
)(form3.flatpickr(_)),
|
||||
form3.group(
|
||||
form("text"),
|
||||
raw("Simul description"),
|
||||
help = frag("Anything you want to tell the participants?").some
|
||||
trans.simulDescription(),
|
||||
help = trans.simulDescriptionHelp().some
|
||||
)(form3.textarea(_)(rows := 10)),
|
||||
ctx.me.exists(_.canBeFeatured) option form3.checkbox(
|
||||
form("featured"),
|
||||
frag("Feature on lichess.org/simul"),
|
||||
help = frag("Show your simul to everyone on lichess.org/simul. Disable for private simuls.").some
|
||||
trans.simulFeatured("lichess.org/simul"),
|
||||
help = trans.simulFeaturedHelp("lichess.org/simul").some
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -205,7 +205,10 @@ object contact {
|
|||
Leaf(
|
||||
"casual",
|
||||
noRatingPoints(),
|
||||
p(ratedGame())
|
||||
frag(
|
||||
p(ratedGame()),
|
||||
botRatingAbuse()
|
||||
)
|
||||
),
|
||||
Leaf(
|
||||
"error-page",
|
||||
|
|
|
@ -14,14 +14,14 @@ object form {
|
|||
|
||||
def create(form: Form[_], teamId: TeamID)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "New Swiss tournament",
|
||||
title = trans.swiss.newSwiss.txt(),
|
||||
moreCss = cssTag("swiss.form"),
|
||||
moreJs = jsModule("tourForm")
|
||||
) {
|
||||
val fields = new SwissFields(form, none)
|
||||
main(cls := "page-small")(
|
||||
div(cls := "swiss__form tour__form box box-pad")(
|
||||
h1("New Swiss tournament"),
|
||||
h1(trans.swiss.newSwiss()),
|
||||
postForm(cls := "form3", action := routes.Swiss.create(teamId))(
|
||||
form3.split(fields.name, fields.nbRounds),
|
||||
form3.split(fields.rated, fields.variant),
|
||||
|
@ -80,33 +80,33 @@ object form {
|
|||
),
|
||||
postForm(cls := "terminate", action := routes.Swiss.terminate(swiss.id.value))(
|
||||
submitButton(dataIcon := "", cls := "text button button-red confirm")(
|
||||
"Cancel the tournament"
|
||||
trans.cancelTournament()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def condition(form: Form[_], fields: SwissFields, swiss: Option[Swiss])(implicit ctx: Context) =
|
||||
private def condition(form: Form[_], fields: SwissFields, swiss: Option[Swiss])(implicit ctx: Context) =
|
||||
frag(
|
||||
form3.split(
|
||||
form3.group(form("conditions.nbRatedGame.nb"), frag("Minimum rated games"), half = true)(
|
||||
form3.group(form("conditions.nbRatedGame.nb"), trans.minimumRatedGames(), half = true)(
|
||||
form3.select(_, SwissCondition.DataForm.nbRatedGameChoices)
|
||||
),
|
||||
(ctx.me.exists(_.hasTitle) || isGranted(_.ManageTournament)) ?? {
|
||||
form3.checkbox(
|
||||
form("conditions.titled"),
|
||||
frag("Only titled players"),
|
||||
help = frag("Require an official title to join the tournament").some,
|
||||
trans.onlyTitled(),
|
||||
help = trans.onlyTitledHelp().some,
|
||||
half = true
|
||||
)
|
||||
}
|
||||
),
|
||||
form3.split(
|
||||
form3.group(form("conditions.minRating.rating"), frag("Minimum rating"), half = true)(
|
||||
form3.group(form("conditions.minRating.rating"), trans.minimumRating(), half = true)(
|
||||
form3.select(_, SwissCondition.DataForm.minRatingChoices)
|
||||
),
|
||||
form3.group(form("conditions.maxRating.rating"), frag("Maximum weekly rating"), half = true)(
|
||||
form3.group(form("conditions.maxRating.rating"), trans.maximumWeeklyRating(), half = true)(
|
||||
form3.select(_, SwissCondition.DataForm.maxRatingChoices)
|
||||
)
|
||||
)
|
||||
|
@ -133,8 +133,8 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
|
|||
def nbRounds =
|
||||
form3.group(
|
||||
form("nbRounds"),
|
||||
"Number of rounds",
|
||||
help = raw("An odd number of rounds allows optimal color balance.").some,
|
||||
trans.swiss.nbRounds(),
|
||||
help = trans.swiss.nbRoundsHelp().some,
|
||||
half = true
|
||||
)(
|
||||
form3.input(_, typ = "number")
|
||||
|
@ -145,7 +145,7 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
|
|||
form3.checkbox(
|
||||
form("rated"),
|
||||
trans.rated(),
|
||||
help = raw("Games are rated<br>and impact players ratings").some
|
||||
help = trans.ratedFormHelp().some
|
||||
),
|
||||
st.input(tpe := "hidden", st.name := form("rated").name, value := "false") // hack allow disabling rated
|
||||
)
|
||||
|
@ -167,16 +167,14 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
|
|||
)
|
||||
)
|
||||
def roundInterval =
|
||||
form3.group(form("roundInterval"), frag("Interval between rounds"), half = true)(
|
||||
form3.group(form("roundInterval"), trans.swiss.roundInterval(), half = true)(
|
||||
form3.select(_, SwissForm.roundIntervalChoices)
|
||||
)
|
||||
def description =
|
||||
form3.group(
|
||||
form("description"),
|
||||
frag("Tournament description"),
|
||||
help = frag(
|
||||
"Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)"
|
||||
).some,
|
||||
trans.tournDescription(),
|
||||
help = trans.tournDescriptionHelp().some,
|
||||
half = true
|
||||
)(form3.textarea(_)(rows := 4))
|
||||
def position =
|
||||
|
@ -185,25 +183,25 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
|
|||
trans.startPosition(),
|
||||
klass = "position",
|
||||
half = true,
|
||||
help = views.html.tournament.form.positionInputHelp.some
|
||||
help = trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
|
||||
)(form3.input(_))
|
||||
def startsAt =
|
||||
form3.group(
|
||||
form("startsAt"),
|
||||
frag("Tournament start date"),
|
||||
help = frag("In your own local timezone").some,
|
||||
trans.swiss.tournStartDate(),
|
||||
help = trans.inYourLocalTimezone().some,
|
||||
half = true
|
||||
)(form3.flatpickr(_))
|
||||
|
||||
def chatFor =
|
||||
form3.group(form("chatFor"), frag("Tournament chat"), half = true) { f =>
|
||||
form3.group(form("chatFor"), trans.tournChat(), half = true) { f =>
|
||||
form3.select(
|
||||
f,
|
||||
Seq(
|
||||
Swiss.ChatFor.NONE -> "No chat",
|
||||
Swiss.ChatFor.LEADERS -> "Only team leaders",
|
||||
Swiss.ChatFor.MEMBERS -> "Only team members",
|
||||
Swiss.ChatFor.ALL -> "All Lichess players"
|
||||
Swiss.ChatFor.NONE -> trans.noChat.txt(),
|
||||
Swiss.ChatFor.LEADERS -> trans.onlyTeamLeaders.txt(),
|
||||
Swiss.ChatFor.MEMBERS -> trans.onlyTeamMembers.txt(),
|
||||
Swiss.ChatFor.ALL -> trans.study.everyone.txt()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -219,10 +217,8 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
|
|||
def forbiddenPairings =
|
||||
form3.group(
|
||||
form("forbiddenPairings"),
|
||||
frag("Forbidden pairings"),
|
||||
help = frag(
|
||||
"Usernames of players that must not play together (Siblings, for instance). Two usernames per line, separated by a space."
|
||||
).some,
|
||||
trans.swiss.forbiddenPairings(),
|
||||
help = trans.swiss.forbiddenPairingsHelp().some,
|
||||
half = true
|
||||
)(form3.textarea(_)(rows := 4))
|
||||
}
|
||||
|
|
|
@ -103,8 +103,18 @@ object form {
|
|||
|
||||
private def textFields(form: Form[_])(implicit ctx: Context) = frag(
|
||||
form3.group(form("location"), trans.location())(form3.input(_)),
|
||||
form3.group(form("description"), trans.description())(form3.textarea(_)(rows := 10)),
|
||||
form3.group(form("descPrivate"), trans.descPrivate(), help = trans.descPrivateHelp().some)(
|
||||
form3.group(form("description"), trans.description(), help = markdownAvailable.some)(
|
||||
form3.textarea(_)(rows := 10)
|
||||
),
|
||||
form3.group(
|
||||
form("descPrivate"),
|
||||
trans.descPrivate(),
|
||||
help = frag(
|
||||
trans.descPrivateHelp(),
|
||||
br,
|
||||
markdownAvailable
|
||||
).some
|
||||
)(
|
||||
form3.textarea(_)(rows := 10)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ import lila.app.mashup.TeamInfo
|
|||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.paginator.Paginator
|
||||
import lila.common.String.html.{ markdownLinksOrRichText, richText, safeJsonValue }
|
||||
import lila.common.String.html.{ richText, safeJsonValue }
|
||||
import lila.team.Team
|
||||
|
||||
object show {
|
||||
|
@ -176,7 +176,7 @@ object show {
|
|||
div(cls := "team-show__content__col2")(
|
||||
standardFlash(),
|
||||
st.section(cls := "team-show__desc")(
|
||||
markdownLinksOrRichText {
|
||||
markdown {
|
||||
t.descPrivate.ifTrue(info.mine) | t.description
|
||||
},
|
||||
t.location.map { loc =>
|
||||
|
@ -228,6 +228,18 @@ object show {
|
|||
)
|
||||
}
|
||||
|
||||
private object markdown {
|
||||
import scala.concurrent.duration._
|
||||
private val renderer = new lila.common.Markdown(list = true)
|
||||
private val cache: com.github.blemale.scaffeine.LoadingCache[String, String] =
|
||||
lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterAccess(10 minutes)
|
||||
.maximumSize(512)
|
||||
.build(renderer.apply)
|
||||
|
||||
def apply(text: String): Frag = raw(cache get text)
|
||||
}
|
||||
|
||||
// handle special teams here
|
||||
private def joinButton(t: Team)(implicit ctx: Context) =
|
||||
t.id match {
|
||||
|
|
|
@ -22,7 +22,7 @@ object form {
|
|||
main(cls := "page-small")(
|
||||
div(cls := "tour__form box box-pad")(
|
||||
h1(
|
||||
if (fields.isTeamBattle) "New Team Battle"
|
||||
if (fields.isTeamBattle) trans.arena.newTeamBattle()
|
||||
else trans.createANewTournament()
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Tournament.create)(
|
||||
|
@ -86,7 +86,7 @@ object form {
|
|||
),
|
||||
postForm(cls := "terminate", action := routes.Tournament.terminate(tour.id))(
|
||||
submitButton(dataIcon := "", cls := "text button button-red confirm")(
|
||||
"Cancel the tournament"
|
||||
trans.cancelTournament()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -116,13 +116,13 @@ object form {
|
|||
case None => baseField
|
||||
case Some(team) => baseField.copy(value = team.some)
|
||||
}
|
||||
form3.group(field, frag("Only members of team"), half = true)(
|
||||
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
|
||||
form3.group(field, trans.onlyMembersOfTeam(), half = true)(
|
||||
form3.select(_, List(("", trans.noRestriction.txt())) ::: teams.map(_.pair))
|
||||
)
|
||||
}
|
||||
),
|
||||
form3.split(
|
||||
form3.group(form("conditions.nbRatedGame.nb"), frag("Minimum rated games"), half = true)(
|
||||
form3.group(form("conditions.nbRatedGame.nb"), trans.minimumRatedGames(), half = true)(
|
||||
form3.select(_, Condition.DataForm.nbRatedGameChoices)
|
||||
),
|
||||
autoField(auto, form("conditions.nbRatedGame.perf")) { field =>
|
||||
|
@ -132,7 +132,7 @@ object form {
|
|||
}
|
||||
),
|
||||
form3.split(
|
||||
form3.group(form("conditions.minRating.rating"), frag("Minimum rating"), half = true)(
|
||||
form3.group(form("conditions.minRating.rating"), trans.minimumRating(), half = true)(
|
||||
form3.select(_, Condition.DataForm.minRatingChoices)
|
||||
),
|
||||
autoField(auto, form("conditions.minRating.perf")) { field =>
|
||||
|
@ -140,7 +140,7 @@ object form {
|
|||
}
|
||||
),
|
||||
form3.split(
|
||||
form3.group(form("conditions.maxRating.rating"), frag("Maximum weekly rating"), half = true)(
|
||||
form3.group(form("conditions.maxRating.rating"), trans.maximumWeeklyRating(), half = true)(
|
||||
form3.select(_, Condition.DataForm.maxRatingChoices)
|
||||
),
|
||||
autoField(auto, form("conditions.maxRating.perf")) { field =>
|
||||
|
@ -151,15 +151,15 @@ object form {
|
|||
(ctx.me.exists(_.hasTitle) || isGranted(_.ManageTournament)) ?? {
|
||||
form3.checkbox(
|
||||
form("conditions.titled"),
|
||||
frag("Only titled players"),
|
||||
help = frag("Require an official title to join the tournament").some,
|
||||
trans.onlyTitled(),
|
||||
help = trans.onlyTitledHelp().some,
|
||||
half = true
|
||||
)
|
||||
},
|
||||
form3.checkbox(
|
||||
form("berserkable"),
|
||||
frag("Allow Berserk"),
|
||||
help = frag("Let players halve their clock time to gain an extra point").some,
|
||||
trans.arena.allowBerserk(),
|
||||
help = trans.arena.allowBerserkHelp().some,
|
||||
half = true
|
||||
),
|
||||
form3.hidden(form("berserkable"), "false".some) // hack to allow disabling berserk
|
||||
|
@ -168,14 +168,14 @@ object form {
|
|||
form3.checkbox(
|
||||
form("hasChat"),
|
||||
trans.chatRoom(),
|
||||
help = frag("Let players discuss in a chat room").some,
|
||||
help = trans.arena.allowChatHelp().some,
|
||||
half = true
|
||||
),
|
||||
form3.hidden(form("hasChat"), "false".some), // hack to allow disabling chat
|
||||
form3.checkbox(
|
||||
form("streakable"),
|
||||
frag("Arena streaks"),
|
||||
help = frag("After 2 wins, consecutive wins grant 4 points instead of 2.").some,
|
||||
trans.arena.arenaStreaks(),
|
||||
help = trans.arena.arenaStreaksHelp().some,
|
||||
half = true
|
||||
),
|
||||
form3.hidden(form("streakable"), "false".some) // hack to allow disabling streaks
|
||||
|
@ -186,18 +186,6 @@ object form {
|
|||
form3.input(field)(
|
||||
tour.exists(t => !t.isCreated && t.position.isEmpty).option(disabled := true)
|
||||
)
|
||||
|
||||
val positionInputHelp = frag(
|
||||
"Paste a valid FEN to start every game from a given position.",
|
||||
br,
|
||||
"It only works for standard games, not with variants.",
|
||||
br,
|
||||
"You can use the ",
|
||||
a(href := routes.Editor.index, target := "_blank")("board editor"),
|
||||
" to generate a FEN position, then paste it here.",
|
||||
br,
|
||||
"Leave empty to start games from the normal initial position."
|
||||
)
|
||||
}
|
||||
|
||||
final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit ctx: Context) {
|
||||
|
@ -228,7 +216,7 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
|
|||
form3.checkbox(
|
||||
form("rated"),
|
||||
trans.rated(),
|
||||
help = raw("Games are rated<br>and impact players ratings").some
|
||||
help = trans.ratedFormHelp().some
|
||||
),
|
||||
st.input(tpe := "hidden", st.name := form("rated").name, value := "false") // hack allow disabling rated
|
||||
)
|
||||
|
@ -246,7 +234,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
|
|||
trans.startPosition(),
|
||||
klass = "position",
|
||||
half = true,
|
||||
help = tournament.form.positionInputHelp.some
|
||||
help =
|
||||
trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
|
||||
)(
|
||||
views.html.tournament.form.startingPosition(_, tour)
|
||||
)
|
||||
|
@ -270,10 +259,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
|
|||
def description(half: Boolean) =
|
||||
form3.group(
|
||||
form("description"),
|
||||
frag("Tournament description"),
|
||||
help = frag(
|
||||
"Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)"
|
||||
).some,
|
||||
trans.tournDescription(),
|
||||
help = trans.tournDescriptionHelp().some,
|
||||
half = half
|
||||
)(form3.textarea(_)(rows := 4))
|
||||
def password =
|
||||
|
@ -286,10 +273,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
|
|||
def startDate =
|
||||
form3.group(
|
||||
form("startDate"),
|
||||
frag("Custom start date"),
|
||||
help = frag(
|
||||
"""In your own local timezone. This overrides the "Time before tournament starts" setting"""
|
||||
).some
|
||||
trans.arena.customStartDate(),
|
||||
help = trans.arena.customStartDateHelp().some
|
||||
)(form3.flatpickr(_))
|
||||
def advancedSettings =
|
||||
frag(
|
||||
|
|
|
@ -26,7 +26,7 @@ class Report:
|
|||
|
||||
|
||||
def short_lang(lang):
|
||||
if lang in ["ne-NP", "la-LA", "nn-NO", "zh-CN", "ur-PK", "zh-TW", "tlh-AA", "ml-IN", "pt-BR", "tt-RU"]:
|
||||
if lang in ["ne-NP", "la-LA", "nn-NO", "zh-CN", "ur-PK", "zh-TW", "tlh-AA", "ml-IN", "pt-BR", "tt-RU", "de-CH"]:
|
||||
return lang.replace("-", "").lower()
|
||||
elif lang == "kab-DZ":
|
||||
return "kaby"
|
||||
|
|
|
@ -415,7 +415,7 @@ fishnet {
|
|||
}
|
||||
offline_mode = true # any client can provide moves and analysis
|
||||
actor.name = fishnet
|
||||
analysis.nodes = 2000000 # nnue
|
||||
analysis.nodes = 2100000 # nnue
|
||||
move.plies = 300
|
||||
client_min_version = "2.1.3"
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ object BlogTransform {
|
|||
private type Text = String
|
||||
private type Html = String
|
||||
|
||||
private val renderer = new lila.common.Markdown
|
||||
private val renderer = new lila.common.Markdown(table = true)
|
||||
|
||||
private val cache: LoadingCache[Text, Html] = lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterWrite(15 minutes)
|
||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
|||
|
||||
final class ClasMarkup {
|
||||
|
||||
private val renderer = new lila.common.Markdown(autoLink = true)
|
||||
private val renderer = new lila.common.Markdown(autoLink = true, list = true)
|
||||
|
||||
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterAccess(20 minutes)
|
||||
|
|
|
@ -4,6 +4,8 @@ import lila.user.User
|
|||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.common.SecureRandom
|
||||
|
||||
case class Student(
|
||||
_id: Student.Id, // userId:clasId
|
||||
userId: User.ID,
|
||||
|
@ -50,10 +52,9 @@ object Student {
|
|||
|
||||
private[clas] object password {
|
||||
|
||||
private val random = new java.security.SecureRandom()
|
||||
private val chars = ('2' to '9') ++ (('a' to 'z').toSet - 'l') mkString
|
||||
private val nbChars = chars.length
|
||||
private def secureChar = chars(random nextInt nbChars)
|
||||
private def secureChar = chars(SecureRandom nextInt nbChars)
|
||||
|
||||
def generate =
|
||||
User.ClearPassword {
|
||||
|
|
|
@ -98,7 +98,10 @@ final class CoachApi(
|
|||
_.refreshAfterWrite(1 hour)
|
||||
.buildAsyncFuture { _ =>
|
||||
userRepo.coll.secondaryPreferred
|
||||
.distinctEasy[String, Set]("profile.country", $doc("roles" -> lila.security.Permission.Coach.dbKey, "enabled" -> true))
|
||||
.distinctEasy[String, Set](
|
||||
"profile.country",
|
||||
$doc("roles" -> lila.security.Permission.Coach.dbKey, "enabled" -> true)
|
||||
)
|
||||
}
|
||||
}
|
||||
def allCountries: Fu[Set[String]] = countriesCache.get {}
|
||||
|
|
|
@ -32,53 +32,55 @@ final class CoachPager(
|
|||
def selector = listableSelector ++ lang.?? { l => $doc("languages" -> l.code) }
|
||||
|
||||
val adapter = country match {
|
||||
case Some(country) => new AdapterLike[Coach.WithUser] {
|
||||
def nbResults: Fu[Int] = coll.secondaryPreferred.countSel(selector)
|
||||
case Some(country) =>
|
||||
new AdapterLike[Coach.WithUser] {
|
||||
def nbResults: Fu[Int] = coll.secondaryPreferred.countSel(selector)
|
||||
|
||||
def slice(offset: Int, length: Int): Fu[List[Coach.WithUser]] =
|
||||
coll
|
||||
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
|
||||
import framework._
|
||||
Match(selector) -> List(
|
||||
Sort(
|
||||
order match {
|
||||
case Alphabetical => Ascending("_id")
|
||||
case NbReview => Descending("nbReview")
|
||||
case LichessRating => Descending("user.rating")
|
||||
case Login => Descending("user.seenAt")
|
||||
}
|
||||
),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> userRepo.coll.name,
|
||||
"localField" -> "_id",
|
||||
"foreignField" -> "_id",
|
||||
"as" -> "_user"
|
||||
def slice(offset: Int, length: Int): Fu[List[Coach.WithUser]] =
|
||||
coll
|
||||
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
|
||||
import framework._
|
||||
Match(selector) -> List(
|
||||
Sort(
|
||||
order match {
|
||||
case Alphabetical => Ascending("_id")
|
||||
case NbReview => Descending("nbReview")
|
||||
case LichessRating => Descending("user.rating")
|
||||
case Login => Descending("user.seenAt")
|
||||
}
|
||||
),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> userRepo.coll.name,
|
||||
"localField" -> "_id",
|
||||
"foreignField" -> "_id",
|
||||
"as" -> "_user"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
UnwindField("_user"),
|
||||
Match($doc("_user.profile.country" -> country.code)),
|
||||
Skip(offset),
|
||||
Limit(length)
|
||||
)
|
||||
}
|
||||
.map { docs =>
|
||||
for {
|
||||
doc <- docs
|
||||
coach <- doc.asOpt[Coach]
|
||||
user <- doc.getAsOpt[User]("_user")
|
||||
} yield Coach.WithUser(coach, user)
|
||||
}
|
||||
}
|
||||
),
|
||||
UnwindField("_user"),
|
||||
Match($doc("_user.profile.country" -> country.code)),
|
||||
Skip(offset),
|
||||
Limit(length)
|
||||
)
|
||||
}
|
||||
.map { docs =>
|
||||
for {
|
||||
doc <- docs
|
||||
coach <- doc.asOpt[Coach]
|
||||
user <- doc.getAsOpt[User]("_user")
|
||||
} yield Coach.WithUser(coach, user)
|
||||
}
|
||||
}
|
||||
|
||||
case None => new Adapter[Coach](
|
||||
collection = coll,
|
||||
selector = selector,
|
||||
projection = none,
|
||||
sort = order.predicate
|
||||
) mapFutureList withUsers
|
||||
case None =>
|
||||
new Adapter[Coach](
|
||||
collection = coll,
|
||||
selector = selector,
|
||||
projection = none,
|
||||
sort = order.predicate
|
||||
) mapFutureList withUsers
|
||||
}
|
||||
|
||||
Paginator(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package lila.common
|
||||
|
||||
import ornicar.scalalib.Random
|
||||
import play.api.mvc._
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
|
@ -12,7 +11,7 @@ final class LilaCookie(domain: NetDomain, baker: SessionCookieBaker) {
|
|||
|
||||
def makeSessionId(implicit req: RequestHeader) = session(LilaCookie.sessionId, generateSessionId())
|
||||
|
||||
def generateSessionId() = Random secureString 22
|
||||
def generateSessionId() = SecureRandom nextString 22
|
||||
|
||||
def session(name: String, value: String)(implicit req: RequestHeader): Cookie =
|
||||
withSession { s =>
|
||||
|
|
|
@ -8,22 +8,41 @@ import com.vladsch.flexmark.parser.Parser
|
|||
import com.vladsch.flexmark.util.data.MutableDataSet
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
final class Markdown(autoLink: Boolean = false) {
|
||||
final class Markdown(
|
||||
autoLink: Boolean = true,
|
||||
table: Boolean = false,
|
||||
strikeThrough: Boolean = false,
|
||||
header: Boolean = false,
|
||||
blockQuote: Boolean = false,
|
||||
list: Boolean = false
|
||||
) {
|
||||
|
||||
private type Text = String
|
||||
private type Html = String
|
||||
|
||||
private val extensions: java.util.List[Parser.ParserExtension] = List(
|
||||
TablesExtension.create().some,
|
||||
StrikethroughExtension.create().some,
|
||||
table option TablesExtension.create(),
|
||||
strikeThrough option StrikethroughExtension.create(),
|
||||
autoLink option AutolinkExtension.create()
|
||||
).flatten.asJava
|
||||
|
||||
private val options = new MutableDataSet()
|
||||
|
||||
options.set(Parser.EXTENSIONS, extensions)
|
||||
options.set(TablesExtension.CLASS_NAME, "slist")
|
||||
options.set(HtmlRenderer.ESCAPE_HTML, Boolean.box(true))
|
||||
options.set(HtmlRenderer.ESCAPE_HTML, Boolean box true)
|
||||
options.set(HtmlRenderer.SOFT_BREAK, "<br>")
|
||||
|
||||
// always disabled
|
||||
options.set(Parser.HTML_BLOCK_PARSER, Boolean box false)
|
||||
options.set(Parser.INDENTED_CODE_BLOCK_PARSER, Boolean box false)
|
||||
options.set(Parser.FENCED_CODE_BLOCK_PARSER, Boolean box false)
|
||||
|
||||
// configurable
|
||||
if (table) options.set(TablesExtension.CLASS_NAME, "slist")
|
||||
if (!header) options.set(Parser.HEADING_PARSER, Boolean box false)
|
||||
if (!blockQuote) options.set(Parser.BLOCK_QUOTE_PARSER, Boolean box false)
|
||||
if (!list) options.set(Parser.LIST_BLOCK_PARSER, Boolean box false)
|
||||
|
||||
private val parser = Parser.builder(options).build()
|
||||
private val renderer = HtmlRenderer.builder(options).build()
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package lila.common
|
||||
|
||||
import ornicar.scalalib.Random
|
||||
|
||||
case class Nonce(value: String) extends AnyVal with StringValue {
|
||||
def scriptSrc = s"'nonce-$value'"
|
||||
}
|
||||
|
||||
object Nonce {
|
||||
|
||||
def random: Nonce = Nonce(Random.secureString(24))
|
||||
def random: Nonce = Nonce(SecureRandom.nextString(24))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package lila.common
|
||||
|
||||
import scala.collection.mutable.StringBuilder
|
||||
|
||||
abstract class Random {
|
||||
|
||||
protected def current: java.util.Random
|
||||
|
||||
def nextBoolean(): Boolean = current.nextBoolean()
|
||||
def nextDouble(): Double = current.nextDouble()
|
||||
def nextFloat(): Float = current.nextFloat()
|
||||
def nextInt(): Int = current.nextInt()
|
||||
def nextInt(n: Int): Int = current.nextInt(n)
|
||||
def nextLong(): Long = current.nextLong()
|
||||
def nextGaussian(): Double = current.nextGaussian()
|
||||
|
||||
def nextBytes(len: Int): Array[Byte] = {
|
||||
val bytes = new Array[Byte](len)
|
||||
current.nextBytes(bytes)
|
||||
bytes
|
||||
}
|
||||
|
||||
private def nextAlphanumeric(): Char = {
|
||||
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
chars charAt nextInt(chars.length) // Constant time
|
||||
}
|
||||
|
||||
def nextString(len: Int): String = {
|
||||
val sb = new StringBuilder(len)
|
||||
for (_ <- 0 until len) sb += nextAlphanumeric()
|
||||
sb.result()
|
||||
}
|
||||
|
||||
def shuffle[T, C](xs: IterableOnce[T])(implicit bf: scala.collection.BuildFrom[xs.type, T, C]): C =
|
||||
new scala.util.Random(current).shuffle(xs)
|
||||
|
||||
def oneOf[A](vec: Vector[A]): Option[A] =
|
||||
vec.nonEmpty ?? {
|
||||
vec lift nextInt(vec.size)
|
||||
}
|
||||
}
|
||||
|
||||
object ThreadLocalRandom extends Random {
|
||||
override def current = java.util.concurrent.ThreadLocalRandom.current
|
||||
}
|
||||
|
||||
object SecureRandom extends Random {
|
||||
override val current = new java.security.SecureRandom()
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package lila.common
|
||||
|
||||
import scala.collection.mutable.StringBuilder
|
||||
|
||||
object ThreadLocalRandom {
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom.current
|
||||
|
||||
def nextBoolean(): Boolean = current.nextBoolean()
|
||||
def nextBytes(bytes: Array[Byte]): Unit = current.nextBytes(bytes)
|
||||
def nextDouble(): Double = current.nextDouble()
|
||||
def nextFloat(): Float = current.nextFloat()
|
||||
def nextInt(): Int = current.nextInt()
|
||||
def nextInt(n: Int): Int = current.nextInt(n)
|
||||
def nextLong(): Long = current.nextLong()
|
||||
def nextGaussian(): Double = current.nextGaussian()
|
||||
def nextChar(): Char = {
|
||||
val i = nextInt(62)
|
||||
if (i < 26) i + 65
|
||||
else if (i < 52) i + 71
|
||||
else i - 4
|
||||
}.toChar
|
||||
def shuffle[T, C](xs: IterableOnce[T])(implicit bf: scala.collection.BuildFrom[xs.type, T, C]): C =
|
||||
new scala.util.Random(current).shuffle(xs)
|
||||
def nextString(len: Int): String = {
|
||||
val sb = new StringBuilder(len)
|
||||
for (_ <- 0 until len) sb += nextChar()
|
||||
sb.result()
|
||||
}
|
||||
def oneOf[A](vec: Vector[A]): Option[A] =
|
||||
vec.nonEmpty ?? {
|
||||
vec lift nextInt(vec.size)
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ case class AssetVersion(value: String) extends AnyVal with StringValue
|
|||
object AssetVersion {
|
||||
var current = random
|
||||
def change() = { current = random }
|
||||
private def random = AssetVersion(ornicar.scalalib.Random secureString 6)
|
||||
private def random = AssetVersion(SecureRandom nextString 6)
|
||||
}
|
||||
|
||||
case class IsMobile(value: Boolean) extends AnyVal with BooleanValue
|
||||
|
|
|
@ -95,5 +95,5 @@ final class EvalCacheApi(
|
|||
}
|
||||
|
||||
private def destSize(fen: FEN): Int =
|
||||
chess.Game(chess.variant.Standard.some, fen.some).situation.destinations.size
|
||||
chess.Game(chess.variant.Standard.some, fen.some).situation.moves.view.map(_._2.size).sum
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package lila.fishnet
|
||||
|
||||
import ornicar.scalalib.Random
|
||||
import com.gilt.gfc.semver.SemVer
|
||||
import lila.common.IpAddress
|
||||
import lila.common.{ IpAddress, SecureRandom }
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
@ -98,5 +97,5 @@ object Client {
|
|||
}
|
||||
}
|
||||
|
||||
def makeKey = Key(Random.secureString(8))
|
||||
def makeKey = Key(SecureRandom.nextString(8))
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ import scala.concurrent.duration._
|
|||
|
||||
import chess.{ Black, Clock, White }
|
||||
|
||||
import lila.common.Future
|
||||
import lila.common.{ Future, ThreadLocalRandom }
|
||||
import lila.game.{ Game, GameRepo, UciMemo }
|
||||
import ornicar.scalalib.Random.approximately
|
||||
|
||||
final class FishnetPlayer(
|
||||
redis: FishnetRedis,
|
||||
|
@ -43,7 +42,7 @@ final class FishnetPlayer(
|
|||
sleep = (delay * accel) atMost 500
|
||||
if sleep > 25
|
||||
millis = sleep * 10
|
||||
randomized = approximately(0.5f)(millis)
|
||||
randomized = millis + millis * (ThreadLocalRandom.nextDouble() - 0.5)
|
||||
divided = randomized / (if (g.turns > 9) 1 else 2)
|
||||
} yield divided.millis
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package lila.game
|
||||
|
||||
import chess.Color
|
||||
import java.security.SecureRandom
|
||||
import ornicar.scalalib.Random
|
||||
|
||||
import lila.common.{ SecureRandom, ThreadLocalRandom }
|
||||
import lila.db.dsl._
|
||||
|
||||
final class IdGenerator(gameRepo: GameRepo)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
@ -32,16 +31,15 @@ final class IdGenerator(gameRepo: GameRepo)(implicit ec: scala.concurrent.Execut
|
|||
|
||||
object IdGenerator {
|
||||
|
||||
private[this] val secureRandom = new SecureRandom()
|
||||
private[this] val whiteSuffixChars = ('0' to '4') ++ ('A' to 'Z') mkString
|
||||
private[this] val blackSuffixChars = ('5' to '9') ++ ('a' to 'z') mkString
|
||||
|
||||
def uncheckedGame: Game.ID = lila.common.ThreadLocalRandom nextString Game.gameIdSize
|
||||
def uncheckedGame: Game.ID = ThreadLocalRandom nextString Game.gameIdSize
|
||||
|
||||
def player(color: Color): Player.ID = {
|
||||
// Trick to avoid collisions between player ids in the same game.
|
||||
val suffixChars = color.fold(whiteSuffixChars, blackSuffixChars)
|
||||
val suffix = suffixChars(secureRandom nextInt suffixChars.length)
|
||||
Random.secureString(Game.playerIdSize - 1) + suffix
|
||||
val suffix = suffixChars(SecureRandom nextInt suffixChars.length)
|
||||
SecureRandom.nextString(Game.playerIdSize - 1) + suffix
|
||||
}
|
||||
}
|
||||
|
|
|
@ -319,6 +319,7 @@ val `membersOnly` = new I18nKey("membersOnly")
|
|||
val `boardEditor` = new I18nKey("boardEditor")
|
||||
val `setTheBoard` = new I18nKey("setTheBoard")
|
||||
val `popularOpenings` = new I18nKey("popularOpenings")
|
||||
val `endgamePositions` = new I18nKey("endgamePositions")
|
||||
val `startPosition` = new I18nKey("startPosition")
|
||||
val `clearBoard` = new I18nKey("clearBoard")
|
||||
val `savePosition` = new I18nKey("savePosition")
|
||||
|
@ -712,6 +713,31 @@ val `welcome` = new I18nKey("welcome")
|
|||
val `lichessPatronInfo` = new I18nKey("lichessPatronInfo")
|
||||
val `coachManager` = new I18nKey("coachManager")
|
||||
val `streamerManager` = new I18nKey("streamerManager")
|
||||
val `cancelTournament` = new I18nKey("cancelTournament")
|
||||
val `tournDescription` = new I18nKey("tournDescription")
|
||||
val `tournDescriptionHelp` = new I18nKey("tournDescriptionHelp")
|
||||
val `ratedFormHelp` = new I18nKey("ratedFormHelp")
|
||||
val `onlyMembersOfTeam` = new I18nKey("onlyMembersOfTeam")
|
||||
val `noRestriction` = new I18nKey("noRestriction")
|
||||
val `minimumRatedGames` = new I18nKey("minimumRatedGames")
|
||||
val `minimumRating` = new I18nKey("minimumRating")
|
||||
val `maximumWeeklyRating` = new I18nKey("maximumWeeklyRating")
|
||||
val `onlyTitled` = new I18nKey("onlyTitled")
|
||||
val `onlyTitledHelp` = new I18nKey("onlyTitledHelp")
|
||||
val `positionInputHelp` = new I18nKey("positionInputHelp")
|
||||
val `cancelSimul` = new I18nKey("cancelSimul")
|
||||
val `simulHostcolor` = new I18nKey("simulHostcolor")
|
||||
val `estimatedStart` = new I18nKey("estimatedStart")
|
||||
val `simulFeatured` = new I18nKey("simulFeatured")
|
||||
val `simulFeaturedHelp` = new I18nKey("simulFeaturedHelp")
|
||||
val `simulDescription` = new I18nKey("simulDescription")
|
||||
val `simulDescriptionHelp` = new I18nKey("simulDescriptionHelp")
|
||||
val `markdownAvailable` = new I18nKey("markdownAvailable")
|
||||
val `inYourLocalTimezone` = new I18nKey("inYourLocalTimezone")
|
||||
val `tournChat` = new I18nKey("tournChat")
|
||||
val `noChat` = new I18nKey("noChat")
|
||||
val `onlyTeamLeaders` = new I18nKey("onlyTeamLeaders")
|
||||
val `onlyTeamMembers` = new I18nKey("onlyTeamMembers")
|
||||
val `opponentLeftCounter` = new I18nKey("opponentLeftCounter")
|
||||
val `mateInXHalfMoves` = new I18nKey("mateInXHalfMoves")
|
||||
val `nextCaptureOrPawnMoveInXHalfMoves` = new I18nKey("nextCaptureOrPawnMoveInXHalfMoves")
|
||||
|
@ -776,6 +802,14 @@ val `thisIsPrivate` = new I18nKey("arena:thisIsPrivate")
|
|||
val `shareUrl` = new I18nKey("arena:shareUrl")
|
||||
val `drawStreak` = new I18nKey("arena:drawStreak")
|
||||
val `history` = new I18nKey("arena:history")
|
||||
val `newTeamBattle` = new I18nKey("arena:newTeamBattle")
|
||||
val `customStartDate` = new I18nKey("arena:customStartDate")
|
||||
val `customStartDateHelp` = new I18nKey("arena:customStartDateHelp")
|
||||
val `allowBerserk` = new I18nKey("arena:allowBerserk")
|
||||
val `allowBerserkHelp` = new I18nKey("arena:allowBerserkHelp")
|
||||
val `allowChatHelp` = new I18nKey("arena:allowChatHelp")
|
||||
val `arenaStreaks` = new I18nKey("arena:arenaStreaks")
|
||||
val `arenaStreaksHelp` = new I18nKey("arena:arenaStreaksHelp")
|
||||
val `drawingWithinNbMoves` = new I18nKey("arena:drawingWithinNbMoves")
|
||||
val `viewAllXTeams` = new I18nKey("arena:viewAllXTeams")
|
||||
}
|
||||
|
@ -1238,7 +1272,6 @@ val `nothingHere` = new I18nKey("class:nothingHere")
|
|||
val `newsEdit1` = new I18nKey("class:newsEdit1")
|
||||
val `newsEdit2` = new I18nKey("class:newsEdit2")
|
||||
val `newsEdit3` = new I18nKey("class:newsEdit3")
|
||||
val `markdownAvailable` = new I18nKey("class:markdownAvailable")
|
||||
val `nbPendingInvitations` = new I18nKey("class:nbPendingInvitations")
|
||||
val `nbTeachers` = new I18nKey("class:nbTeachers")
|
||||
val `nbStudents` = new I18nKey("class:nbStudents")
|
||||
|
@ -1297,6 +1330,7 @@ val `fideMate` = new I18nKey("contact:fideMate")
|
|||
val `knightMate` = new I18nKey("contact:knightMate")
|
||||
val `noRatingPoints` = new I18nKey("contact:noRatingPoints")
|
||||
val `ratedGame` = new I18nKey("contact:ratedGame")
|
||||
val `botRatingAbuse` = new I18nKey("contact:botRatingAbuse")
|
||||
val `errorPage` = new I18nKey("contact:errorPage")
|
||||
val `reportErrorPage` = new I18nKey("contact:reportErrorPage")
|
||||
val `banAppeal` = new I18nKey("contact:banAppeal")
|
||||
|
@ -1567,6 +1601,7 @@ val `inputMovesWithTheKeyboard` = new I18nKey("preferences:inputMovesWithTheKeyb
|
|||
val `snapArrowsToValidMoves` = new I18nKey("preferences:snapArrowsToValidMoves")
|
||||
val `sayGgWpAfterLosingOrDrawing` = new I18nKey("preferences:sayGgWpAfterLosingOrDrawing")
|
||||
val `yourPreferencesHaveBeenSaved` = new I18nKey("preferences:yourPreferencesHaveBeenSaved")
|
||||
val `scrollOnTheBoardToReplayMoves` = new I18nKey("preferences:scrollOnTheBoardToReplayMoves")
|
||||
}
|
||||
|
||||
object team {
|
||||
|
@ -1845,6 +1880,13 @@ val `swissTournaments` = new I18nKey("swiss:swissTournaments")
|
|||
val `roundsAreStartedManually` = new I18nKey("swiss:roundsAreStartedManually")
|
||||
val `startingIn` = new I18nKey("swiss:startingIn")
|
||||
val `nextRound` = new I18nKey("swiss:nextRound")
|
||||
val `tournStartDate` = new I18nKey("swiss:tournStartDate")
|
||||
val `nbRounds` = new I18nKey("swiss:nbRounds")
|
||||
val `nbRoundsHelp` = new I18nKey("swiss:nbRoundsHelp")
|
||||
val `roundInterval` = new I18nKey("swiss:roundInterval")
|
||||
val `forbiddenPairings` = new I18nKey("swiss:forbiddenPairings")
|
||||
val `forbiddenPairingsHelp` = new I18nKey("swiss:forbiddenPairingsHelp")
|
||||
val `newSwiss` = new I18nKey("swiss:newSwiss")
|
||||
val `viewAllXRounds` = new I18nKey("swiss:viewAllXRounds")
|
||||
val `nbRounds` = new I18nKey("swiss:nbRounds")
|
||||
val `oneRoundEveryXDays` = new I18nKey("swiss:oneRoundEveryXDays")
|
||||
|
@ -2096,6 +2138,7 @@ val `waitingForMorePlayers` = new I18nKey("storm:waitingForMorePlayers")
|
|||
val `raceComplete` = new I18nKey("storm:raceComplete")
|
||||
val `spectating` = new I18nKey("storm:spectating")
|
||||
val `joinTheRace` = new I18nKey("storm:joinTheRace")
|
||||
val `startTheRace` = new I18nKey("storm:startTheRace")
|
||||
val `yourRankX` = new I18nKey("storm:yourRankX")
|
||||
val `waitForRematch` = new I18nKey("storm:waitForRematch")
|
||||
val `nextRace` = new I18nKey("storm:nextRace")
|
||||
|
|
|
@ -113,7 +113,7 @@ final class IrcApi(
|
|||
text = s":$icon: ${slackdown.linkifyUsers(text)}",
|
||||
channel = s"tavern-monitor-${tpe.toString.toLowerCase}"
|
||||
)
|
||||
val md = s":$icon: ${markdown.linkifyUsers(text)}"
|
||||
val md = s"${markdown.userLink(mod.name)} :$icon: ${markdown.linkifyUsers(text)}"
|
||||
slack(msg) >>
|
||||
slack(msg.copy(channel = SlackClient.rooms.tavernMonitorAll)) >>
|
||||
zulip.mod(s"monitor-${tpe.toString.toLowerCase}")(md) >>
|
||||
|
|
|
@ -278,7 +278,7 @@ final class ModlogApi(repo: ModlogRepo, userRepo: UserRepo, ircApi: IrcApi)(impl
|
|||
import lila.mod.{ Modlog => M }
|
||||
val icon = m.action match {
|
||||
case M.alt | M.engine | M.booster | M.troll | M.closeAccount => "thorhammer"
|
||||
case M.unalt | M.unengine | M.unbooster | M.untroll | M.reopenAccount => "large_blue_circle"
|
||||
case M.unalt | M.unengine | M.unbooster | M.untroll | M.reopenAccount => "blue_circle"
|
||||
case M.deletePost | M.deleteTeam | M.terminateTournament => "x"
|
||||
case M.chatTimeout => "hourglass_flowing_sand"
|
||||
case M.closeTopic | M.disableTeam => "lock"
|
||||
|
|
|
@ -3,6 +3,7 @@ package lila.oauth
|
|||
import org.joda.time.DateTime
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
import lila.common.SecureRandom
|
||||
import lila.user.User
|
||||
|
||||
case class AccessToken(
|
||||
|
@ -22,14 +23,11 @@ case class AccessToken(
|
|||
|
||||
object AccessToken {
|
||||
|
||||
val idSize = 16
|
||||
|
||||
case class Id(value: String) extends AnyVal {
|
||||
def isPersonal = value.lengthIs == idSize
|
||||
case class Id(value: String) extends AnyVal
|
||||
object Id {
|
||||
def random() = Id(s"lio_${SecureRandom.nextString(32)}")
|
||||
}
|
||||
|
||||
def makeId = Id(ornicar.scalalib.Random secureString idSize)
|
||||
|
||||
case class ForAuth(userId: User.ID, scopes: List[OAuthScope])
|
||||
|
||||
case class WithApp(token: AccessToken, app: OAuthApp)
|
||||
|
|
|
@ -15,7 +15,7 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
|
|||
|
||||
def create(granted: AccessTokenRequest.Granted): Fu[AccessToken] = {
|
||||
val token = AccessToken(
|
||||
id = AccessToken.Id(Protocol.Secret.random("lio_").value),
|
||||
id = AccessToken.Id.random(),
|
||||
publicId = BSONObjectID.generate(),
|
||||
clientId = PersonalToken.clientId, // TODO
|
||||
userId = granted.userId,
|
||||
|
|
|
@ -17,7 +17,7 @@ object AccessTokenRequest {
|
|||
for {
|
||||
grantType <- grantType.toValid(Error.GrantTypeRequired).andThen(GrantType.from)
|
||||
code <- code.map(AuthorizationCode.apply).toValid(Error.CodeRequired)
|
||||
codeVerifier <- codeVerifier.map(CodeVerifier.apply).toValid(Error.CodeVerifierRequired)
|
||||
codeVerifier <- codeVerifier.toValid(Error.CodeVerifierRequired).andThen(CodeVerifier.from)
|
||||
redirectUri <- redirectUri.map(UncheckedRedirectUri.apply).toValid(Error.RedirectUriRequired)
|
||||
clientId <- clientId.map(ClientId.apply).toValid(Error.ClientIdRequired)
|
||||
} yield Prepared(grantType, code, codeVerifier, redirectUri, clientId)
|
||||
|
|
|
@ -13,7 +13,7 @@ final class AuthorizationApi(val coll: Coll)(implicit ec: scala.concurrent.Execu
|
|||
val code = Protocol.AuthorizationCode.random()
|
||||
coll.insert.one(
|
||||
PendingAuthorizationBSONHandler write PendingAuthorization(
|
||||
code.secret.hashed,
|
||||
code.hashed,
|
||||
request.clientId,
|
||||
request.user,
|
||||
request.redirectUri,
|
||||
|
@ -27,7 +27,7 @@ final class AuthorizationApi(val coll: Coll)(implicit ec: scala.concurrent.Execu
|
|||
def consume(
|
||||
request: AccessTokenRequest.Prepared
|
||||
): Fu[Validated[Protocol.Error, AccessTokenRequest.Granted]] =
|
||||
coll.findAndModify($doc(F.hashedCode -> request.code.secret.hashed), coll.removeModifier) map {
|
||||
coll.findAndModify($doc(F.hashedCode -> request.code.hashed), coll.removeModifier) map {
|
||||
_.result[PendingAuthorization]
|
||||
.toValid(Protocol.Error.AuthorizationCodeInvalid)
|
||||
.ensure(Protocol.Error.AuthorizationCodeExpired)(_.expires.isAfter(DateTime.now()))
|
||||
|
|
|
@ -5,7 +5,6 @@ import cats.data.Validated
|
|||
import com.roundeights.hasher.Algo
|
||||
import io.lemonlabs.uri.AbsoluteUrl
|
||||
import org.joda.time.DateTime
|
||||
import ornicar.scalalib.Random
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.user.User
|
||||
|
|
|
@ -2,6 +2,7 @@ package lila.oauth
|
|||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.common.SecureRandom
|
||||
import lila.user.User
|
||||
import io.lemonlabs.uri.AbsoluteUrl
|
||||
|
||||
|
@ -21,8 +22,8 @@ object OAuthApp {
|
|||
case class Id(value: String) extends AnyVal
|
||||
case class Secret(value: String) extends AnyVal
|
||||
|
||||
def makeId = Id(ornicar.scalalib.Random secureString 16)
|
||||
def makeSecret = Secret(ornicar.scalalib.Random secureString 32)
|
||||
def makeId = Id(SecureRandom nextString 16)
|
||||
def makeSecret = Secret(SecureRandom nextString 32)
|
||||
|
||||
object BSONFields {
|
||||
val clientId = "client_id"
|
||||
|
|
|
@ -29,7 +29,7 @@ object OAuthForm {
|
|||
) {
|
||||
def make(user: lila.user.User) =
|
||||
AccessToken(
|
||||
id = AccessToken.makeId,
|
||||
id = AccessToken.Id.random(),
|
||||
publicId = BSONObjectID.generate(),
|
||||
clientId = PersonalToken.clientId,
|
||||
userId = user.id,
|
||||
|
|
|
@ -6,26 +6,16 @@ import cats.data.Validated
|
|||
import play.api.libs.json.Json
|
||||
import com.roundeights.hasher.Algo
|
||||
import io.lemonlabs.uri.AbsoluteUrl
|
||||
import ornicar.scalalib.Random
|
||||
|
||||
import lila.common.SecureRandom
|
||||
|
||||
object Protocol {
|
||||
case class Secret(value: String) {
|
||||
def hashed: String = Algo.sha256(value).hex
|
||||
override def toString = "Secret(***)"
|
||||
override def equals(other: Any) = other match {
|
||||
case other: Secret => hashed == other.hashed
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode = hashed.hashCode()
|
||||
case class AuthorizationCode(secret: String) extends AnyVal {
|
||||
def hashed = Algo.sha256(secret).hex
|
||||
override def toString = "AuthorizationCode(***)"
|
||||
}
|
||||
object Secret {
|
||||
def random(prefix: String) = Secret(s"$prefix${Random.secureString(32)}")
|
||||
}
|
||||
|
||||
case class AuthorizationCode(secret: Secret) extends AnyVal
|
||||
object AuthorizationCode {
|
||||
def apply(value: String): AuthorizationCode = AuthorizationCode(Secret(value))
|
||||
def random() = AuthorizationCode(Secret.random("liu_"))
|
||||
def random() = AuthorizationCode(s"liu_${SecureRandom.nextString(32)}")
|
||||
}
|
||||
|
||||
case class ClientId(value: String) extends AnyVal
|
||||
|
@ -50,6 +40,13 @@ object Protocol {
|
|||
Base64.getUrlEncoder().withoutPadding().encodeToString(Algo.sha256(value).bytes)
|
||||
)
|
||||
}
|
||||
object CodeVerifier {
|
||||
def from(value: String): Validated[Error, CodeVerifier] =
|
||||
Validated
|
||||
.valid(value)
|
||||
.ensure(Error.CodeVerifierTooShort)(_.size >= 43)
|
||||
.map(CodeVerifier.apply)
|
||||
}
|
||||
|
||||
case class ResponseType()
|
||||
object ResponseType {
|
||||
|
@ -83,7 +80,7 @@ object Protocol {
|
|||
|
||||
def code(code: AuthorizationCode, state: Option[State]): String = value
|
||||
.withQueryString(
|
||||
"code" -> Some(code.secret.value),
|
||||
"code" -> Some(code.secret),
|
||||
"state" -> state.map(_.value)
|
||||
)
|
||||
.toString
|
||||
|
@ -128,13 +125,14 @@ object Protocol {
|
|||
case object RedirectUriRequired extends InvalidRequest("redirect_uri required")
|
||||
case object RedirectUriInvalid extends InvalidRequest("redirect_uri invalid")
|
||||
case object RedirectSchemeNotAllowed
|
||||
extends InvalidRequest("contact us to get exotic redirect_uri schemes whitelisted")
|
||||
extends InvalidRequest("open a github issue to get exotic redirect_uri schemes whitelisted")
|
||||
case object ResponseTypeRequired extends InvalidRequest("response_type required")
|
||||
case object CodeChallengeRequired extends InvalidRequest("code_challenge required")
|
||||
case object CodeChallengeMethodRequired extends InvalidRequest("code_challenge_method required")
|
||||
case object GrantTypeRequired extends InvalidRequest("grant_type required")
|
||||
case object CodeRequired extends InvalidRequest("code required")
|
||||
case object CodeVerifierRequired extends InvalidRequest("code_verifier required")
|
||||
case object CodeVerifierTooShort extends InvalidRequest("code_verifier too short")
|
||||
|
||||
case class InvalidScope(val key: String) extends Error("invalid_scope") {
|
||||
def description = s"invalid scope: ${URLEncoder.encode(key, "UTF-8")}"
|
||||
|
|
|
@ -39,7 +39,7 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
|
|||
.make(
|
||||
owner = player,
|
||||
puzzles = puzzles.grouped(2).flatMap(_.headOption).toList,
|
||||
countdownSeconds = 10
|
||||
countdownSeconds = 5
|
||||
)
|
||||
store.put(race.id, race)
|
||||
lila.mon.racer.race(lobby = race.isLobby).increment()
|
||||
|
@ -69,18 +69,23 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
|
|||
|
||||
def join(id: RacerRace.Id, player: RacerPlayer.Id): Option[RacerRace] =
|
||||
get(id).flatMap(_ join player) map { r =>
|
||||
val race = start(r) | r
|
||||
val race = (r.isLobby ?? doStart(r)) | r
|
||||
saveAndPublish(race)
|
||||
race
|
||||
}
|
||||
|
||||
private def start(race: RacerRace): Option[RacerRace] = race.startCountdown.map { starting =>
|
||||
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
|
||||
finish(race.id)
|
||||
}
|
||||
starting
|
||||
private[racer] def manualStart(race: RacerRace): Unit = !race.isLobby ?? {
|
||||
doStart(race) foreach saveAndPublish
|
||||
}
|
||||
|
||||
private def doStart(race: RacerRace): Option[RacerRace] =
|
||||
race.startCountdown.map { starting =>
|
||||
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
|
||||
finish(race.id)
|
||||
}
|
||||
starting
|
||||
}
|
||||
|
||||
private def finish(id: RacerRace.Id): Unit =
|
||||
get(id) foreach { race =>
|
||||
lila.mon.racer.players(lobby = race.isLobby).record(race.players.size)
|
||||
|
|
|
@ -27,7 +27,8 @@ final class RacerJson(stormJson: StormJson, sign: StormSign, lightUserSync: Ligh
|
|||
.add("lobby", race.isLobby),
|
||||
"player" -> player,
|
||||
"puzzles" -> race.puzzles
|
||||
) ++ state(race)
|
||||
)
|
||||
.add("owner", race.owner == player.id) ++ state(race)
|
||||
|
||||
// socket updates
|
||||
def state(race: RacerRace) = Json
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package lila.racer
|
||||
|
||||
|
||||
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
||||
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
||||
import play.api.libs.json.{ JsObject, Json }
|
||||
|
@ -28,6 +29,12 @@ final private class RacerSocket(
|
|||
api.join(raceId, playerId).unit
|
||||
case Protocol.In.PlayerScore(raceId, playerId, score) =>
|
||||
api.registerPlayerScore(raceId, playerId, score)
|
||||
case Protocol.In.RaceStart(raceId, playerId) =>
|
||||
api
|
||||
.get(raceId)
|
||||
.filter(_.startsAt.isEmpty)
|
||||
.filter(_.owner == playerId)
|
||||
.foreach(api.manualStart)
|
||||
}
|
||||
|
||||
remoteSocketApi.subscribe("racer-in", Protocol.In.reader)(
|
||||
|
@ -45,6 +52,7 @@ object RacerSocket {
|
|||
|
||||
case class PlayerJoin(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
|
||||
case class PlayerScore(race: RacerRace.Id, player: RacerPlayer.Id, score: Int) extends P.In
|
||||
case class RaceStart(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
|
||||
|
||||
val reader: P.In.Reader = raw => raceReader(raw) orElse RP.In.reader(raw)
|
||||
|
||||
|
@ -58,6 +66,10 @@ object RacerSocket {
|
|||
raw.get(3) { case Array(raceId, playerId, scoreStr) =>
|
||||
scoreStr.toIntOption map { PlayerScore(RacerRace.Id(raceId), RacerPlayer.Id(playerId), _) }
|
||||
}
|
||||
case "racer/start" =>
|
||||
raw.get(2) { case Array(raceId, playerId) =>
|
||||
RaceStart(RacerRace.Id(raceId), RacerPlayer.Id(playerId)).some
|
||||
}
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,14 @@ import scala.concurrent.duration._
|
|||
|
||||
final class RelayMarkup {
|
||||
|
||||
private val renderer = new lila.common.Markdown(autoLink = true)
|
||||
private val renderer =
|
||||
new lila.common.Markdown(
|
||||
autoLink = true,
|
||||
list = true,
|
||||
table = true,
|
||||
strikeThrough = true,
|
||||
header = true
|
||||
)
|
||||
|
||||
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
|
||||
.expireAfterAccess(20 minutes)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package lila.security
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import ornicar.scalalib.Random
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import play.api.data.validation.{ Constraint, Valid => FormValid, Invalid, ValidationError }
|
||||
|
@ -12,7 +11,7 @@ import reactivemongo.api.ReadPreference
|
|||
import scala.annotation.nowarn
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest, IpAddress }
|
||||
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest, IpAddress, SecureRandom }
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
import lila.oauth.{ AccessToken, OAuthScope, OAuthServer }
|
||||
|
@ -94,7 +93,7 @@ final class SecurityApi(
|
|||
userRepo mustConfirmEmail userId flatMap {
|
||||
case true => fufail(SecurityApi MustConfirmEmail userId)
|
||||
case false =>
|
||||
val sessionId = Random secureString 22
|
||||
val sessionId = SecureRandom nextString 22
|
||||
if (tor isExitNode HTTPRequest.ipAddress(req)) logger.info(s"Tor login $userId")
|
||||
store.save(sessionId, userId, req, apiVersion, up = true, fp = none) inject sessionId
|
||||
}
|
||||
|
@ -102,7 +101,7 @@ final class SecurityApi(
|
|||
def saveSignup(userId: User.ID, apiVersion: Option[ApiVersion], fp: Option[FingerPrint])(implicit
|
||||
req: RequestHeader
|
||||
): Funit = {
|
||||
val sessionId = lila.common.ThreadLocalRandom nextString 22
|
||||
val sessionId = SecureRandom nextString 22
|
||||
store.save(s"SIG-$sessionId", userId, req, apiVersion, up = false, fp = fp)
|
||||
}
|
||||
|
||||
|
@ -211,7 +210,7 @@ final class SecurityApi(
|
|||
sessionId.startsWith(prefix) ?? store.getIfPresent(sessionId)
|
||||
|
||||
def saveAuthentication(userId: User.ID)(implicit req: RequestHeader): Fu[SessionId] = {
|
||||
val sessionId = s"$prefix${Random secureString 22}"
|
||||
val sessionId = s"$prefix${SecureRandom nextString 22}"
|
||||
store.put(sessionId, userId)
|
||||
logger.info(s"Appeal login by $userId")
|
||||
fuccess(sessionId)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package lila.user
|
||||
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.{ IvParameterSpec, SecretKeySpec }
|
||||
import com.roundeights.hasher.Implicits._
|
||||
|
||||
import lila.common.SecureRandom
|
||||
import lila.common.config.Secret
|
||||
|
||||
/** Encryption for bcrypt hashes.
|
||||
|
@ -54,14 +54,12 @@ final private class PasswordHasher(
|
|||
import org.mindrot.BCrypt
|
||||
import User.ClearPassword
|
||||
|
||||
private val prng = new SecureRandom()
|
||||
private val aes = new Aes(secret)
|
||||
private val aes = new Aes(secret)
|
||||
private def bHash(salt: Array[Byte], p: ClearPassword) =
|
||||
hashTimer(BCrypt.hashpwRaw(p.value.sha512, 'a', logRounds, salt))
|
||||
|
||||
def hash(p: ClearPassword): HashedPassword = {
|
||||
val salt = new Array[Byte](16)
|
||||
prng.nextBytes(salt)
|
||||
val salt = SecureRandom.nextBytes(16)
|
||||
HashedPassword(salt ++ aes.encrypt(Aes.iv(salt), bHash(salt, p)))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package lila.user
|
||||
|
||||
import org.apache.commons.codec.binary.Base32
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.nio.ByteBuffer
|
||||
import org.apache.commons.codec.binary.Base32
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
import lila.common.SecureRandom
|
||||
|
||||
import User.TotpToken
|
||||
|
||||
case class TotpSecret(secret: Array[Byte]) extends AnyVal {
|
||||
|
@ -45,15 +46,9 @@ object TotpSecret {
|
|||
"0" * (6 - s.length) + s
|
||||
}
|
||||
|
||||
private[this] val secureRandom = new SecureRandom()
|
||||
|
||||
def apply(base32: String) = new TotpSecret(new Base32().decode(base32))
|
||||
|
||||
def random: TotpSecret = {
|
||||
val secret = new Array[Byte](20)
|
||||
secureRandom.nextBytes(secret)
|
||||
TotpSecret(secret)
|
||||
}
|
||||
def random = TotpSecret(SecureRandom.nextBytes(20))
|
||||
|
||||
private[user] val totpSecretBSONHandler = lila.db.dsl.quickHandler[TotpSecret](
|
||||
{ case v: BSONBinary => TotpSecret(v.byteArray) },
|
||||
|
|
|
@ -22,7 +22,7 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
|
|||
val normalize = User normalize _
|
||||
|
||||
def topNbGame(nb: Int): Fu[List[User]] =
|
||||
coll.find(enabledSelect).sort($sort desc "count.game").cursor[User]().list(nb)
|
||||
coll.find(enabledNoBotSelect).sort($sort desc "count.game").cursor[User]().list(nb)
|
||||
|
||||
def byId(id: ID): Fu[Option[User]] = User.noGhost(id) ?? coll.byId[User](id)
|
||||
|
||||
|
@ -277,6 +277,7 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
|
|||
$doc(F.marks -> UserMark.Boost.key),
|
||||
$doc(F.marks -> UserMark.Troll.key)
|
||||
)
|
||||
val enabledNoBotSelect = enabledSelect ++ $doc(F.title $ne Title.BOT)
|
||||
def stablePerfSelect(perf: String) =
|
||||
$doc(s"perfs.$perf.gl.d" -> $lt(lila.rating.Glicko.provisionalDeviation))
|
||||
val patronSelect = $doc(s"${F.plan}.active" -> true)
|
||||
|
@ -391,8 +392,6 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
|
|||
|
||||
def updateTroll(user: User) = setTroll(user.id, user.marks.troll)
|
||||
|
||||
def isEngine(id: ID): Fu[Boolean] = coll.exists($id(id) ++ engineSelect(true))
|
||||
|
||||
def filterEngine(ids: Seq[ID]): Fu[Set[ID]] =
|
||||
coll.distinct[ID, Set]("_id", Some($inIds(ids) ++ engineSelect(true)))
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ object Dependencies {
|
|||
val scalalib = "com.github.ornicar" %% "scalalib" % "7.0.2"
|
||||
val hasher = "com.roundeights" %% "hasher" % "1.2.1"
|
||||
val jodaTime = "joda-time" % "joda-time" % "2.10.10"
|
||||
val chess = "org.lichess" %% "scalachess" % "10.2.2"
|
||||
val chess = "org.lichess" %% "scalachess" % "10.2.4"
|
||||
val compression = "org.lichess" %% "compression" % "1.6"
|
||||
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.3.1-THIB"
|
||||
val prismic = "io.prismic" %% "scala-kit" % "1.2.19-THIB213"
|
||||
|
@ -22,7 +22,7 @@ object Dependencies {
|
|||
val autoconfig = "io.methvin.play" %% "autoconfig-macros" % "0.3.2" % "provided"
|
||||
val scalatest = "org.scalatest" %% "scalatest" % "3.1.0" % Test
|
||||
val uaparser = "org.uaparser" %% "uap-scala" % "0.13.0"
|
||||
val specs2 = "org.specs2" %% "specs2-core" % "4.12.1" % Test
|
||||
val specs2 = "org.specs2" %% "specs2-core" % "4.12.2" % Test
|
||||
val apacheText = "org.apache.commons" % "commons-text" % "1.9"
|
||||
val bloomFilter = "com.github.alexandrnikitin" %% "bloom-filter" % "0.13.1"
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ lichess.advantageChart = function (data, trans, el) {
|
|||
events: {
|
||||
click: function (event) {
|
||||
if (event.point) {
|
||||
event.point.select();
|
||||
event.point.select(true);
|
||||
lichess.pubsub.emit('analysis.chart.click', event.point.x);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -27,12 +27,12 @@
|
|||
<item quantity="other">Spillede %1$s træk</item>
|
||||
</plurals>
|
||||
<plurals name="inNbCorrespondenceGames">
|
||||
<item quantity="one">i %1$s korrespondance-spil</item>
|
||||
<item quantity="other">i %1$s korrespondance-spil</item>
|
||||
<item quantity="one">i %1$s korrespondanceparti</item>
|
||||
<item quantity="other">i %1$s korrespondancepartier</item>
|
||||
</plurals>
|
||||
<plurals name="completedNbGames">
|
||||
<item quantity="one">Afsluttede %s korrespondancespil</item>
|
||||
<item quantity="other">Afsluttede %s korrespondancespil</item>
|
||||
<item quantity="one">Afsluttede %s korrespondanceparti</item>
|
||||
<item quantity="other">Afsluttede %s korrespondancepartier</item>
|
||||
</plurals>
|
||||
<plurals name="followedNbPlayers">
|
||||
<item quantity="one">Begyndte at følge %s spiller</item>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="activity">Verlauf</string>
|
||||
<string name="hostedALiveStream">Hät en livestream gmacht</string>
|
||||
<string name="hostedALiveStream">Hät en liveschtriim gmacht</string>
|
||||
<plurals name="supportedNbMonths">
|
||||
<item quantity="one">Unterstützt lichess.org sit %1$s Monät als %2$s</item>
|
||||
<item quantity="other">Unterstützt lichess.org seit %1$s Monaten als %2$s</item>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="activity">Գործունեություն</string>
|
||||
<plurals name="competedInNbTournaments">
|
||||
<item quantity="one">Ավարտված է %s «Արենա» մրցաշարը</item>
|
||||
<item quantity="other">Ավարտված են %s «Արենա» մրցաշարերը</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,10 @@
|
|||
<item quantity="one">Вижте отбора</item>
|
||||
<item quantity="other">Вижте всички %s отбора</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Нова отборна битка</string>
|
||||
<string name="customStartDate">Начална дата</string>
|
||||
<string name="allowBerserk">Позволи Берсерк</string>
|
||||
<string name="allowChatHelp">Позволи на играчите да обсъждат в чата</string>
|
||||
<string name="arenaStreaks">Последователности в арената</string>
|
||||
<string name="arenaStreaksHelp">След 2 победи, всяка последователна победа носи 4 точки вместо 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -41,4 +41,6 @@ Juga ràpid i torna al vestíbul per jugar més partits i guanyar més punts.</s
|
|||
<item quantity="one">Veure l\'equip</item>
|
||||
<item quantity="other">Veure els %s equips</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nova batalla d\'equips</string>
|
||||
<string name="allowBerserk">Es permet Berserk</string>
|
||||
</resources>
|
||||
|
|
|
@ -24,7 +24,7 @@ Bersærk giver kun ekstra point, hvis du laver mindst 7 træk i partiet.</string
|
|||
<string name="howIsTheWinnerDecided">Hvordan findes vinderen?</string>
|
||||
<string name="howIsTheWinnerDecidedAnswer">Spilleren (eller spillerne) med flest point, når turneringens fastsatte tidsgrænse nås, erklæres for vinder(e).</string>
|
||||
<string name="howDoesPairingWork">Hvordan foretages pardannelse?</string>
|
||||
<string name="howDoesPairingWorkAnswer">Ved starten på turneringen parres spillere på baggrund af deres rating. Vend tilbage til turneringslobbyen, når du afslutter et parti: så vil du igen blive parret med en spiller tæt på din score. Det sikrer minimal ventetid, men du møder måske ikke alle andre deltagere i turneringen.
|
||||
<string name="howDoesPairingWorkAnswer">Ved starten på turneringen parres spillere på baggrund af deres rating. Vend tilbage til turneringslobbyen, når du afslutter et parti: så vil du igen blive parret med en spiller tæt på din ranking. Det sikrer minimal ventetid, men du møder måske ikke alle andre deltagere i turneringen.
|
||||
Spil hurtigt og vend tilbage til lobbyen for at spille flere partier og vinde flere point.</string>
|
||||
<string name="howDoesItEnd">Hvordan afsluttes det?</string>
|
||||
<string name="howDoesItEndAnswer">Turneringen har et nedtællingsur. Når det rammer nul fastfryses turneringsplaceringer, og en vinder kåres. Igangværende partier skal spilles færdig, men de tæller ikke med i turneringen.</string>
|
||||
|
@ -42,4 +42,12 @@ Spil hurtigt og vend tilbage til lobbyen for at spille flere partier og vinde fl
|
|||
<item quantity="one">Se holdet</item>
|
||||
<item quantity="other">Se alle %s hold</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Ny holdkamp</string>
|
||||
<string name="customStartDate">Tilpasset startdato</string>
|
||||
<string name="customStartDateHelp">I din egen lokale tidszone. Dette tilsidesætter \"Tid før turnering starter\" indstillingen</string>
|
||||
<string name="allowBerserk">Tillad Berserk</string>
|
||||
<string name="allowBerserkHelp">Lad spillerne halvere deres tid på uret for at få et ekstra point</string>
|
||||
<string name="allowChatHelp">Lad spillere diskutere i et chatrum</string>
|
||||
<string name="arenaStreaks">Arena-stime</string>
|
||||
<string name="arenaStreaksHelp">Efter 2 sejre giver fortløbende sejre 4 point i stedet for 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -29,8 +29,7 @@ Wenn zwei oder meh Schpiller/-innä di gliich Punktzahl bsitzäd, entscheidät d
|
|||
Sobald du es Schpiil beändät häsch, chasch du zur Turnierübersicht zrugggoh. Dir wird dänn än Schpiller/ ä Schpillerin mit ähnlichäm Rang zuäteilt. So wird ä churzi Warteziit erreicht, allerdings cha passiärä, dass du nöd geg alli anderä Schpiller/-innä im Turnier spillsch.
|
||||
Prässiär und gohn zur Turnierübersicht zrugg, zum meh Schpiil z\'schpilä und ä höcheri Punktzahl z\'erziilä.</string>
|
||||
<string name="howDoesItEnd">Wänn ändät äsTurnier?</string>
|
||||
<string name="howDoesItEndAnswer">WährendmäTurnier wird d\'Ziit abäzellt. Wenn si null erreicht, wird d\'Ranglischte iigfrorä und dä Gwünner bekanntgeh.
|
||||
Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</string>
|
||||
<string name="howDoesItEndAnswer">WährendmäTurnier wird d\'Ziit abäzellt. Wenn si null erreicht, wird d\'Ranglischte iigfrorä und dä Gwünner bekanntgeh. Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</string>
|
||||
<string name="otherRules">Anderi wichtigi Reglä</string>
|
||||
<string name="thereIsACountdown">Es git en Countdown für din erschtä Zug. Züchsch du nöd innerhalb vo därä Zit, wird dä Siig dim Gegner zuegsprochä. Usserdem bechömäd Zürcher immer ein Punkt Abzug.</string>
|
||||
<plurals name="drawingWithinNbMoves">
|
||||
|
@ -45,4 +44,12 @@ Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</
|
|||
<item quantity="one">Das Team ansehen</item>
|
||||
<item quantity="other">Alli %s Teams aluägä</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Neuer Teamkampf</string>
|
||||
<string name="customStartDate">Benutzerdefiniertes Startdatum</string>
|
||||
<string name="customStartDateHelp">In deiner eigenen, lokalen Zeitzone. Dies wird die Einstellung \"Zeit bevor das Turnier beginnt\" überschreiben</string>
|
||||
<string name="allowBerserk">Berserk erlauben</string>
|
||||
<string name="allowBerserkHelp">Lasse Spieler ihre Bedenkzeit halbieren, um einen zusätzlichen Punkt zu erzielen</string>
|
||||
<string name="allowChatHelp">Lasse Spieler in einem Chatraum diskutieren</string>
|
||||
<string name="arenaStreaks">Arena-Siegesserien</string>
|
||||
<string name="arenaStreaksHelp">Nach 2 Siegen gewähren aufeinander folgende Siege 4 Punkte anstelle von 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -29,8 +29,7 @@ Wenn zwei oder mehr Spieler die gleiche Punktzahl besitzen, entscheidet die Turn
|
|||
Sobald du ein Spiel beendet hast, kannst du zur Turnierübersicht zurückkehren. Dir wird dann ein Spieler mit ähnlichem Rang zugeteilt. Dadurch wird eine kurze Wartezeit erreicht, allerdings kann es sein, dass du nicht gegen alle anderen Spieler in diesem Turnier spielen wirst.
|
||||
Spiele schnell und kehre zur Turnierübersicht zurück, um mehr Spiele zu spielen und eine höhere Punktzahl zu erzielen.</string>
|
||||
<string name="howDoesItEnd">Wann endet das Turnier?</string>
|
||||
<string name="howDoesItEndAnswer">Während des Turniers wird die Zeit heruntergezählt. Wenn sie null erreicht, wird die Rangliste eingefroren und der Gewinner bekanntgegeben.
|
||||
Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnier.</string>
|
||||
<string name="howDoesItEndAnswer">Während des Turniers wird die Zeit heruntergezählt. Wenn sie null erreicht, wird die Rangliste eingefroren und der Gewinner bekanntgegeben. Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnier.</string>
|
||||
<string name="otherRules">Andere wichtige Regeln</string>
|
||||
<string name="thereIsACountdown">Es gibt einen Countdown für deinen ersten Zug. Ziehst du nicht innerhalb dieser Zeit, wird der Sieg deinem Gegner zugesprochen.</string>
|
||||
<plurals name="drawingWithinNbMoves">
|
||||
|
@ -45,4 +44,12 @@ Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnie
|
|||
<item quantity="one">Das Team ansehen</item>
|
||||
<item quantity="other">Alle %s Teams ansehen</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Neuer Teamkampf</string>
|
||||
<string name="customStartDate">Benutzerdefiniertes Startdatum</string>
|
||||
<string name="customStartDateHelp">In deiner eigenen, lokalen Zeitzone. Dies wird die Einstellung \"Zeit bevor das Turnier beginnt\" überschreiben</string>
|
||||
<string name="allowBerserk">Berserk erlauben</string>
|
||||
<string name="allowBerserkHelp">Lasse Spieler ihre Bedenkzeit halbieren, um einen zusätzlichen Punkt zu erzielen</string>
|
||||
<string name="allowChatHelp">Lasse Spieler in einem Chatraum diskutieren</string>
|
||||
<string name="arenaStreaks">Arena-Siegesserien</string>
|
||||
<string name="arenaStreaksHelp">Nach 2 Siegen gewähren aufeinander folgende Siege 4 Punkte anstelle von 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -44,4 +44,12 @@ To Berserk δεν ισχύει για παρτίδες με μηδενικό α
|
|||
<item quantity="one">Δείτε την ομάδα</item>
|
||||
<item quantity="other">Δείτε όλες τις %s ομάδες</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Νέα ομαδική μάχη</string>
|
||||
<string name="customStartDate">Προσαρμοσμένη ημερομηνία έναρξης</string>
|
||||
<string name="customStartDateHelp">Στην τοπική ζώνη ώρας σας. Αυτό υπερισχύει της ρύθμισης \"Χρόνος προτού ξεκινήσει το πρωτάθλημα\"</string>
|
||||
<string name="allowBerserk">Να επιτρέπεται το Berserk</string>
|
||||
<string name="allowBerserkHelp">Να επιτρέπεται στους παίκτες να μειώσουν τον χρόνο στο ρολόι τους κατά το ήμισυ για να κερδίσουν έναν επιπλέον πόντο</string>
|
||||
<string name="allowChatHelp">Να επιτρέπεται στους παίκτες να συζητούν σε δωμάτιο συνομιλίας</string>
|
||||
<string name="arenaStreaks">Arena streaks</string>
|
||||
<string name="arenaStreaksHelp">Μετά από 2 νίκες, επιπλέον διαδοχικές νίκες σας 4 πόντους αντί για 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -45,4 +45,12 @@ Play fast and return to the lobby to play more games and win more points.</strin
|
|||
<item quantity="one">View the team</item>
|
||||
<item quantity="other">View all %s teams</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">New Team Battle</string>
|
||||
<string name="customStartDate">Custom start date</string>
|
||||
<string name="customStartDateHelp">In your own local timezone. This overrides the \"Time before tournament starts\" setting</string>
|
||||
<string name="allowBerserk">Allow Berserk</string>
|
||||
<string name="allowBerserkHelp">Let players halve their clock time to gain an extra point</string>
|
||||
<string name="allowChatHelp">Let players discuss in a chat room</string>
|
||||
<string name="arenaStreaks">Arena streaks</string>
|
||||
<string name="arenaStreaksHelp">After 2 wins, consecutive wins grant 4 points instead of 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -44,4 +44,12 @@ Juega rápido y vuelve al recibidor para jugar más partidas y ganar más puntos
|
|||
<item quantity="one">Ver equipo</item>
|
||||
<item quantity="other">Ver los %s equipos</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nueva batalla de equipo</string>
|
||||
<string name="customStartDate">Fecha de inicio personalizada</string>
|
||||
<string name="customStartDateHelp">En tu propia zona horaria local. Esto reemplaza la configuración de «Tiempo antes de que comience el torneo».</string>
|
||||
<string name="allowBerserk">Permitir Berserk</string>
|
||||
<string name="allowBerserkHelp">Permite a los jugadores reducir el tiempo de la partida a la mitad para ganar un punto extra</string>
|
||||
<string name="allowChatHelp">Permite a los jugadores comunicarse en una sala de chat</string>
|
||||
<string name="arenaStreaks">Rachas de torneos</string>
|
||||
<string name="arenaStreaksHelp">Después de ganar 2 partidas seguidas, cada victoria consecutiva concede 4 puntos en lugar de 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@ Jokatu azkar, itzuli egongelara partida gehiago jokatu eta puntu gehiago irabazt
|
|||
<item quantity="one">Taldea ikusi</item>
|
||||
<item quantity="other">%s taldeak ikusi</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Taldeen Arteko Txapelketa berria</string>
|
||||
<string name="customStartDate">Pertsonalizatutako hasiera data</string>
|
||||
<string name="customStartDateHelp">Zure ordu-zonan. Honek \"Txapeketa hasi aurreko denbora\" ezarpena gainidazten du</string>
|
||||
<string name="allowBerserk">Berserk onartu</string>
|
||||
<string name="allowBerserkHelp">Utzi jokalariei beren denbora erdira jaisten puntu gehigarri bat lortzeko</string>
|
||||
<string name="allowChatHelp">Utzi jokalariei txatean hitz egiten</string>
|
||||
<string name="arenaStreaks">Arena boladak</string>
|
||||
<string name="arenaStreaksHelp">Bi garaipenen ostean, jarraian datozen garaipenek 4 puntu emango dituzte 2 eman beharrean.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="arenaTournaments">مسابقات</string>
|
||||
<string name="isItRated">آیا رسمی است؟</string>
|
||||
<string name="willBeNotified">هنگام شروع تورنومنت به شما اطلاع داده خواهد شد، پس در زمان انتظار بازی کردن در تب های دیگر بلامانع است.</string>
|
||||
<string name="isRated">این تورنومنت رسمی است و بر روی ریتینگ شما تاثیر میگذارد.</string>
|
||||
|
@ -25,10 +26,17 @@
|
|||
<string name="howDoesPairingWorkAnswer">در ابتدای تورنومنت، بازیکنان با توجه به امتیازهایشان با یکدیگر روبه رو میشوند.
|
||||
به محض اینکه شما بازی را تمام کردید و به لابی تورنومنت برگردید: شما به بازیکنی که رتبه نزدیک تری به شما دارد متصل میشوید. این مکانیزم در حداقل زمان ممکن انجام میشود، هرچند امکان دارد با همه بازیکنان تورنومنت روبه رو نشوید.
|
||||
سریع بازی کنید و به لابی برگردید تا بازی های بیشتری بازی کنید و امتیاز بیشتری بگیرید.</string>
|
||||
<string name="howDoesItEnd">چگونه پایان میابد؟</string>
|
||||
<string name="howDoesItEndAnswer">تورنومنت یک ساعت شمارش معکوس دارد. وقتی به صفر برسد، رتبه بندی تورنومنت غیرقابل تغییر میشود، و برنده اعلام میشود.
|
||||
بازی های در حال اجرا باید تمام شوند، هر چند نتیجه آن بازی ها تاثیری در تورنومنت ندارد.</string>
|
||||
<string name="howDoesItEnd">چگونه به پایان می رسد؟</string>
|
||||
<string name="howDoesItEndAnswer">تورنومنت یک ساعت شمارش معکوس دارد. وقتی به صفر برسد، رتبه بندی تورنومنت غیرقابل تغییر میشود، و برنده اعلام میشود. بازی های در حال اجرا باید تمام شوند، هر چند نتیجه آن بازی ها تاثیری در تورنومنت ندارد.</string>
|
||||
<string name="otherRules">قوانین مهم دیگر</string>
|
||||
<string name="thisIsPrivate">این یک تورنومنت خصوصی است</string>
|
||||
<string name="shareUrl">این لینک را برای پیوستن دیگران به اشتراک بگذارید.%s</string>
|
||||
<string name="history">تاریخچه مسابقات</string>
|
||||
<string name="newTeamBattle">مبارزه تیمی جدید</string>
|
||||
<string name="customStartDate">تنظیم تاریخ شروع</string>
|
||||
<string name="customStartDateHelp">در منطقه زمانی محلی خودتان. تنظیمات \"زمان قبل شروع شدن مسابقه\" را نادیده می گیرد</string>
|
||||
<string name="allowBerserk">مجاز کردن برسرک</string>
|
||||
<string name="allowBerserkHelp">به بازیکنان اجازه دهید تا زمان خود را نصف کنند تا یک امتیاز اضافی بگیرند</string>
|
||||
<string name="allowChatHelp">اجازه دادن بحث به بازیکنان در چت روم</string>
|
||||
<string name="arenaStreaksHelp">بعد از دو برد، بردهای پی در پی بجای 2 امتیاز 4 امتیاز می دهند.</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@ Pelaa nopeasti ja palaa aulaan, niin voit pelata lisää pelejä ja ansaita lis
|
|||
<item quantity="one">Näytä joukkue</item>
|
||||
<item quantity="other">Näytä kaikki %s joukkuetta</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Uusi joukkuetaisto</string>
|
||||
<string name="customStartDate">Mukautettu alkamisaika</string>
|
||||
<string name="customStartDateHelp">Omalla aikavyöhykkeelläsi. Tämä korvaa asetuksen \"Aika ennen turnauksen alkua\"</string>
|
||||
<string name="allowBerserk">Salli berserkki</string>
|
||||
<string name="allowBerserkHelp">Anna pelaajien puolittaa aika kellossaan ja tavoitella lisäpistettä</string>
|
||||
<string name="allowChatHelp">Anna pelaajien kirjoittaa keskusteluhuoneeseen</string>
|
||||
<string name="arenaStreaks">Areenaputket</string>
|
||||
<string name="arenaStreaksHelp">Kahden voiton jälkeen jokaisesta seuraavasta voitosta saa 4 pistettä (2 pisteen sijaan).</string>
|
||||
</resources>
|
||||
|
|
|
@ -42,4 +42,12 @@ Jouez vite et retournez à la page d\'accueil du tournoi pour jouer plus de part
|
|||
<item quantity="one">Voir l\'équipe</item>
|
||||
<item quantity="other">Voir toutes les équipes %s</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nouveau combat en équipe</string>
|
||||
<string name="customStartDate">Date de début modifiée</string>
|
||||
<string name="customStartDateHelp">Selon votre fuseau horaire. Ce paramètre désactive le paramètre « Temps restant avant le début du tournoi ».</string>
|
||||
<string name="allowBerserk">Activer l\'option Berserk</string>
|
||||
<string name="allowBerserkHelp">Permettre aux joueurs de réduire de moitié leur temps pour gagner un point supplémentaire</string>
|
||||
<string name="allowChatHelp">Permettre aux joueurs de clavarder dans le salon de discussion</string>
|
||||
<string name="arenaStreaks">Série de victoires dans l\'arène</string>
|
||||
<string name="arenaStreaksHelp">Après 2 victoires, des victoires consécutives donnent 4 points au lieu de 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -51,4 +51,12 @@ Imir go tapa agus filleadh chuig an forhalla chun níos mó cluichí a imirt agu
|
|||
<item quantity="many">Féach ar na %s foireann go léir</item>
|
||||
<item quantity="other">Féach ar na %s foireann go léir</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Cath Foirne Nua</string>
|
||||
<string name="customStartDate">Dáta tosaigh saincheaptha</string>
|
||||
<string name="customStartDateHelp">I do chrios ama áitiúil féin. Sáraíonn sé seo an socrú \"Am sula dtosaíonn an comórtas\"</string>
|
||||
<string name="allowBerserk">Lig Berserk</string>
|
||||
<string name="allowBerserkHelp">Lig d’imreoirí a gcuid ama clog a laghdú chun pointe breise a fháil</string>
|
||||
<string name="allowChatHelp">Lig d’imreoirí plé a dhéanamh i seomra comhrá</string>
|
||||
<string name="arenaStreaks">Sruthanna airéine</string>
|
||||
<string name="arenaStreaksHelp">Tar éis 2 bhua, deonaíonn 4 bhua as a chéile 4 phointe in ionad 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@ Xoga rápido e volta ao lobby para xogar máis partidas e gañar máis puntos.</
|
|||
<item quantity="one">Ver o equipo</item>
|
||||
<item quantity="other">Ver todos os %s equipos</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nova batalla por equipos</string>
|
||||
<string name="customStartDate">Fecha de comezo persoalizada</string>
|
||||
<string name="customStartDateHelp">Na túa zona de tempo local. Isto sobreescribe a preferencia \"Tempo antes de que o torneo comece\"</string>
|
||||
<string name="allowBerserk">Berserk permitido</string>
|
||||
<string name="allowBerserkHelp">Deixa ás xogadoras que reduzan á metade o seu tempo para gañar un ponto extra</string>
|
||||
<string name="allowChatHelp">Deixa ás xogadoras falar nunha sala de chat</string>
|
||||
<string name="arenaStreaks">Racha de victorias no Torneo</string>
|
||||
<string name="arenaStreaksHelp">Despois de 2 victorias, as seguintes victorias consecutivas dan 4 pontos en lugar de 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@ Játssz gyorsan, térj vissza a lobbiba további játszmákért, hogy több pont
|
|||
<item quantity="one">Csapat megtekintése</item>
|
||||
<item quantity="other">Mind a %s csapat megtekintése</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Új csapatverseny</string>
|
||||
<string name="customStartDate">Kezdés időpontja</string>
|
||||
<string name="customStartDateHelp">Saját időzóna szerint. Ez felülbírálja a \"Hátralévő idő a verseny kezdetéig\" beállítást</string>
|
||||
<string name="allowBerserk">Berserk engedélyezése</string>
|
||||
<string name="allowBerserkHelp">A játékosok saját idejük feléért cserébe többletpontot kapnak</string>
|
||||
<string name="allowChatHelp">Chatszoba engedélyezése a játékosoknak</string>
|
||||
<string name="arenaStreaks">Aréna sorozatok</string>
|
||||
<string name="arenaStreaksHelp">A második nyert játszmát követően minden nyert játszma 4 pontot ér 2 helyett.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="arenaTournaments">«Արենա» մրցաշարեր</string>
|
||||
<string name="isItRated">Գնահատված է?</string>
|
||||
<string name="willBeNotified">Դուք կտեղեկացվեք մրցաշարի սկզբի մասին, այնպես որ հանգիստ սպասեք:</string>
|
||||
<string name="isRated">Այս մրցաշարում վարկանիշի հաշվարկ կա, այնպես որ այն կազդի Ձեր վարկանիշի վրա:</string>
|
||||
|
@ -26,4 +27,6 @@
|
|||
<string name="howDoesPairingWorkAnswer">Մրցաշարի սկզբում զույգերը կազմվում են ըստ անհատական վարկանիշների:
|
||||
Խաղի ավարտից հետո սեղմեք <<դեպի մրցաշար>> հետ վերադարձի կոճակը, որից հետո Դուք կստանաք նոր մրցակից՝ ըստ Ձեր զբաղեցրած դիրքի: Վիճակահանությունը կտևի ոչ այդքան երկար, սակայն բոլորի հետ խաղալ չեք կարող, միայն Ձեր մոտակայքում գտնվող խաղացողներից կկազմվի զույգը:
|
||||
Խաղացեք արագ և վերադարձեք մրցաշարի հիմնական էջ, որպեսզի խաղաք շատ խաղեր և հավաքեք շատ միավորներ:</string>
|
||||
<string name="drawStreak">Ոչ-ոքիների շարք. եթե խաղացողը «Արենայում» ունի իրար հաջորդող մի քանի ոչ-ոքի, ապա միավոր է տրվում միայն առաջին ոչ-ոքիի համար, կամ այն ոչ-ոքիի համար, որը կտևի %s քայլից ավելի։ Ոչ-ոքիների շարքը կարելի է ընդհատել միայն հաղթանակով, բայց ոչ պարտությամբ կամ ևս մեկ ոչ-ոքիով։</string>
|
||||
<string name="history">«Արենայի» պատմություն</string>
|
||||
</resources>
|
||||
|
|
|
@ -28,4 +28,12 @@
|
|||
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other] %s is the number of teams that can be viewed">
|
||||
<item quantity="other">Lihat semua %s tim</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Pertempuran Tim Baru</string>
|
||||
<string name="customStartDate">Tanggal mulai khusus</string>
|
||||
<string name="customStartDateHelp">Dalam zona waktu lokal Anda sendiri. Ini akan menimpa pengaturan \"Waktu sebelum turnamen dimulai\"</string>
|
||||
<string name="allowBerserk">Izinkan Berserk</string>
|
||||
<string name="allowBerserkHelp">Biarkan pemain memotong waktu jam mereka untuk mendapatkan sebuah poin ekstra</string>
|
||||
<string name="allowChatHelp">Biarkan pemain berdiskusi dalam sebuah ruang obrol</string>
|
||||
<string name="arenaStreaks">Streak arena</string>
|
||||
<string name="arenaStreaksHelp">Setelah 2 kemenangan, secar berurutan akan mendapatkan 4 poin alih-alih 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -35,4 +35,12 @@
|
|||
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other] %s is the number of teams that can be viewed">
|
||||
<item quantity="other">%s チームを表示</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">新しいチームバトル</string>
|
||||
<string name="customStartDate">開始日を指定</string>
|
||||
<string name="customStartDateHelp">自分のタイムゾーンでの日時。これは「トーナメント開始時刻」の設定より優先されます。</string>
|
||||
<string name="allowBerserk">バーサークあり</string>
|
||||
<string name="allowBerserkHelp">持時間半減で 1 ポイントボーナスを認める</string>
|
||||
<string name="allowChatHelp">チャットルームでの会話を認める</string>
|
||||
<string name="arenaStreaks">連勝ボーナスあり</string>
|
||||
<string name="arenaStreaksHelp">2 連勝すると以降の勝ちは 2 ではなく 4 ポイントになる。</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@
|
|||
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other] %s is the number of teams that can be viewed">
|
||||
<item quantity="other">모두 %s개 팀 보기</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">새 팀 배틀</string>
|
||||
<string name="customStartDate">시작시간 설정</string>
|
||||
<string name="customStartDateHelp">당신의 현지 시간대입니다. \"토너먼트 시작까지 시간\" 설정을 덮어씁니다.</string>
|
||||
<string name="allowBerserk">버서크 모드 허용</string>
|
||||
<string name="allowBerserkHelp">플레이어는 자신의 시간을 절반으로 줄이며 대신 추가 승점을 얻습니다</string>
|
||||
<string name="allowChatHelp">플레이어가 채팅방에서 토론할 수 있습니다</string>
|
||||
<string name="arenaStreaks">아레나 연승</string>
|
||||
<string name="arenaStreaksHelp">2승 이후 추가로 연승을 하면 2점이 아니라 4점을 얻습니다.</string>
|
||||
</resources>
|
||||
|
|
|
@ -47,4 +47,12 @@ Kai tik baigiate partiją, grįžkite į turnyro laukiamąjį: tuomet būsite su
|
|||
<item quantity="many">Peržvelgti visas %s komandų</item>
|
||||
<item quantity="other">Peržvelgti visas %s komandų</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nauja komandinė kova</string>
|
||||
<string name="customStartDate">Kita pradžios data</string>
|
||||
<string name="customStartDateHelp">Jūsų laiko zonoje. Turi pirmenybę prieš \"Laikas iki turnyro pradžios\" nustatymą</string>
|
||||
<string name="allowBerserk">Leisti \"įsiutį\"</string>
|
||||
<string name="allowBerserkHelp">Leisti žaidėjams gauti tašką perpus sumažinant laiką ant savo laikrodžio</string>
|
||||
<string name="allowChatHelp">Leisti žaidėjams kalbėtis pokalbių kambaryje</string>
|
||||
<string name="arenaStreaks">Arenos serijos</string>
|
||||
<string name="arenaStreaksHelp">Po dviejų pergalių kiti laimėjimai suteikia keturis taškus vietoje dviejų.</string>
|
||||
</resources>
|
||||
|
|
|
@ -48,4 +48,12 @@ Spēlē ātri un atgriezies vestibilā lai spēlētu vairāk spēļu un iegūtu
|
|||
<item quantity="one">Skatīt %s komandu</item>
|
||||
<item quantity="other">Skatīt visas %s komandas</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Jauna Komandas Kauja</string>
|
||||
<string name="customStartDate">Pielāgots sākuma datums</string>
|
||||
<string name="customStartDateHelp">Jūsu laika zonā. Šis aizstās iestatījumu \"Laiks līdz turnīra sākumam\"</string>
|
||||
<string name="allowBerserk">Atļaut \"dullās\" spēles</string>
|
||||
<string name="allowBerserkHelp">Ļaut spēlētājiem apmainīt pusi laika pret iespēju gūt papildpunktu</string>
|
||||
<string name="allowChatHelp">Ļaut spēlētājiem izmantot sarunu istabu</string>
|
||||
<string name="arenaStreaks">Arēnas uzvaru sērija</string>
|
||||
<string name="arenaStreaksHelp">Pēc 2 uzvarām, secīgās uzvaras piešķirs 4 punktus nevis 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,2 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="newTeamBattle">အသင်းအလိုက် တိုက်ပွဲ အသစ်</string>
|
||||
<string name="customStartDate">ပြိုင်ပွဲ စတင်ရန် စိတ်ကြိုက် သတ်မှတ်သည့် နေ့ရက်</string>
|
||||
<string name="customStartDateHelp">သင့် ဒေသန္တရ အချိန်ဇုံ အရ။ ဤသတ်မှတ်ချက်က \"ပြိုင်ပွဲ မစမီ အချိန်ကာလ\" ဆက်တင်ကို လွှမ်းမိုးသည်</string>
|
||||
<string name="allowBerserk">အသဲအသန် Berserk အချိန်တိုင်း ပုံစံ ခွင့်ပြုသည်</string>
|
||||
<string name="allowBerserkHelp">ခွင့်ပြုချိန် တစ်ဝက် စွန့်လွှတ်ပါက အမှတ် တစ်မှတ် အပို ပေးမည်</string>
|
||||
<string name="allowChatHelp">ကစားသမားတို့ ချက်ထ်ရူးမ် အတွင်း စကားပြောခွင့် ပေးသည်</string>
|
||||
<string name="arenaStreaks">အရီနာပုံစံ ပြိုင်ပွဲ တွတ် သတ်မှတ်ချက်</string>
|
||||
<string name="arenaStreaksHelp">နှစ်ပွဲနိုင်ပြီးနောက် ဆက်တိုက် နိုင်သည့် ပွဲတိုင်း ၂-မှတ် အစား ၄-မှတ် ရမည်။</string>
|
||||
</resources>
|
||||
|
|
|
@ -37,4 +37,12 @@ Berserk gir ett ekstrapoeng bare om du spiller minst 7 trekk i partiet.</string>
|
|||
<item quantity="one">Vis laget</item>
|
||||
<item quantity="other">Vis alle %s lagene</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Ny lagkamp</string>
|
||||
<string name="customStartDate">Egendefinert startdato</string>
|
||||
<string name="customStartDateHelp">I tidssonen din. Dette overstyrer innstillingen «Tid før turnering starter»</string>
|
||||
<string name="allowBerserk">Tillat berserk</string>
|
||||
<string name="allowBerserkHelp">La spillerne halvere tiden sin på klokken for et ekstra poeng</string>
|
||||
<string name="allowChatHelp">La spillerne diskutere i samtalerom</string>
|
||||
<string name="arenaStreaks">Arenarekker</string>
|
||||
<string name="arenaStreaksHelp">Etter 2 seiere gir påfølgende seiere 4 poeng i stedet for 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -43,4 +43,12 @@ Speel snel en ga terug naar de toernooilobby om meer partijen te spelen en meer
|
|||
<item quantity="one">Bekijk het team</item>
|
||||
<item quantity="other">Bekijk alle %s teams</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nieuwe wedstrijd</string>
|
||||
<string name="customStartDate">Aangepaste startdatum</string>
|
||||
<string name="customStartDateHelp">In je eigen lokale tijdzone. Dit overschrijft de instelling \"Tijd voordat het toernooi begint\"</string>
|
||||
<string name="allowBerserk">Berserk toestaan</string>
|
||||
<string name="allowBerserkHelp">Spelers kunnen met de helft van de tijd spelen om een extra punt te krijgen</string>
|
||||
<string name="allowChatHelp">Spelers kunnen chatten in de chat room</string>
|
||||
<string name="arenaStreaks">Arena streaks</string>
|
||||
<string name="arenaStreaksHelp">Na 2 overwinningen geven opeenvolgende overwinningen 4 punten in plaats van 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -37,4 +37,12 @@ Berserk gjev eitt ekstrapoeng berre om du spelar minst 7 trekk i partiet.</strin
|
|||
<item quantity="one">Vis laget</item>
|
||||
<item quantity="other">Vis alle %s lag</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Ny lagkamp</string>
|
||||
<string name="customStartDate">Eigendefinert startdato</string>
|
||||
<string name="customStartDateHelp">I din tidssone. Dette overstyrar innstillinga «Tid før turnering startar»</string>
|
||||
<string name="allowBerserk">Tillat berserk</string>
|
||||
<string name="allowBerserkHelp">Lat spelarane halvere tida si på klokka for eit ekstra poeng</string>
|
||||
<string name="allowChatHelp">Lat spelarane diskutera i samtalerom</string>
|
||||
<string name="arenaStreaks">Arena-sigersrekkjer</string>
|
||||
<string name="arenaStreaksHelp">Etter to sigrar gjev seinara sigrar fire poeng i staden for to.</string>
|
||||
</resources>
|
||||
|
|
|
@ -47,4 +47,12 @@ Graj szybko i wracaj do poczekalni, by rozegrać więcej partii i zdobyć więce
|
|||
<item quantity="many">Zobacz wszystkie %s zespołów</item>
|
||||
<item quantity="other">Zobacz wszystkie %s zespołów</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nowa bitwa klubów</string>
|
||||
<string name="customStartDate">Niestandardowa data rozpoczęcia</string>
|
||||
<string name="customStartDateHelp">W Twojej lokalnej strefie czasowej. Nadpisuje ustawienie \"Czas przed rozpoczęciem turnieju\"</string>
|
||||
<string name="allowBerserk">Zezwalaj na Berserk</string>
|
||||
<string name="allowBerserkHelp">Pozwól graczom na zmniejszenie o połowę czasu pozwalając na zdobycie dodatkowego punktu</string>
|
||||
<string name="allowChatHelp">Pozwól graczom dyskutować na czacie</string>
|
||||
<string name="arenaStreaks">Serie aren</string>
|
||||
<string name="arenaStreaksHelp">Po dwóch zwycięstwach, kolejne zwycięstwa dają 4 punkty zamiast 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -42,4 +42,12 @@ Assim que terminar a sua partida, você voltará a aguardar o emparceiramento co
|
|||
<item quantity="one">Ver a equipe</item>
|
||||
<item quantity="other">Ver todas as %s equipes</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Nova batalha de equipes</string>
|
||||
<string name="customStartDate">Mudar a data de início</string>
|
||||
<string name="customStartDateHelp">No seu próprio fuso horário. Isso irá cancelar a configuração \"Contagem regressiva para início do torneio\"</string>
|
||||
<string name="allowBerserk">Permitir Berserk</string>
|
||||
<string name="allowBerserkHelp">Os jogadores poderão reduzir seu tempo pela metade para ganhar um ponto extra</string>
|
||||
<string name="allowChatHelp">Permite que os jogadores discutam em uma sala de chat</string>
|
||||
<string name="arenaStreaks">Sequências de vitória em arena</string>
|
||||
<string name="arenaStreaksHelp">Após ganhar 2 partidas, vitórias consecutivas valem 4 pontos, ao invés de 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -44,4 +44,12 @@ Joga rápido e volta ao vestíbulo para jogares mais jogos e ganhares mais ponto
|
|||
<item quantity="one">Ver a equipa</item>
|
||||
<item quantity="other">Ver todas as %s equipas</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Batalha de Equipas</string>
|
||||
<string name="customStartDate">Data inicial personalizada</string>
|
||||
<string name="customStartDateHelp">No seu próprio fuso horário local. Substitui a configuração \"Tempo antes do torneio começar\"</string>
|
||||
<string name="allowBerserk">Permitir Berserk</string>
|
||||
<string name="allowBerserkHelp">Permitir aos jogadores ganhar um ponto extra ao reduzir a duração do relógio a metade</string>
|
||||
<string name="allowChatHelp">Permitir que os jogadores discutam numa sala de bate-papo</string>
|
||||
<string name="arenaStreaks">Séries de Arena</string>
|
||||
<string name="arenaStreaksHelp">Depois de 2 vitórias, vitórias consecutivas concedem 4 pontos em vez de 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -45,4 +45,7 @@ Joacă rapid și întoarce-te la lobby pentru a juca mai multe meciuri și pentr
|
|||
<item quantity="few">Vezi toate cele %s echipe</item>
|
||||
<item quantity="other">Vezi toate cele %s de echipe</item>
|
||||
</plurals>
|
||||
<string name="customStartDate">Data de început personalizată</string>
|
||||
<string name="allowBerserkHelp">Lăsați jucătorii să își înjumătățească timpul pentru a obține un punct în plus</string>
|
||||
<string name="allowChatHelp">Permiteți jucătorilor să discute într-o cameră de chat</string>
|
||||
</resources>
|
||||
|
|
|
@ -41,4 +41,12 @@
|
|||
<item quantity="many">Посмотреть все %s клубов</item>
|
||||
<item quantity="other">Посмотреть все %s клубов</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Новый межклубный турнир</string>
|
||||
<string name="customStartDate">Особая дата начала</string>
|
||||
<string name="customStartDateHelp">В вашем часовом поясе. Это переопределяет настройку «Время до начала турнира»</string>
|
||||
<string name="allowBerserk">Разрешить Берсерк</string>
|
||||
<string name="allowBerserkHelp">Разрешает игрокам получать дополнительные очки, если они уменьшат своё время наполовину</string>
|
||||
<string name="allowChatHelp">Разрешить игрокам обсуждение в чате</string>
|
||||
<string name="arenaStreaks">Серии Арены</string>
|
||||
<string name="arenaStreaksHelp">После двух побед подряд каждая следующая победа даёт не 2 очка, а 4.</string>
|
||||
</resources>
|
||||
|
|
|
@ -41,4 +41,12 @@ Igrajte hitro in se vrnite v čakalno vrsto v igralni dvorani, ter si tako zagot
|
|||
<string name="shareUrl">Delite ta URL, da se igralci lahko pridružijo: %s</string>
|
||||
<string name="drawStreak">Niz remijev: Ko igralec zeporedoma remizira v areni, bo le leprvi remi rezultiral v točko ali remiji z več kot %s potezami. Niz remijev se lahko prekine le z zmago, ne pa s porazom ali remijem.</string>
|
||||
<string name="history">Zgodovina arene</string>
|
||||
<string name="newTeamBattle">Nova skupinska bitka</string>
|
||||
<string name="customStartDate">Začetni datum po meri</string>
|
||||
<string name="customStartDateHelp">V svojem lokalnem časovnem pasu. To preglasi nastavitev \"Čas pred začetkom turnirja\"</string>
|
||||
<string name="allowBerserk">Dovoli divjanje</string>
|
||||
<string name="allowBerserkHelp">Naj igralci razpolovijo svoj čas ure, da pridobijo dodatno točko</string>
|
||||
<string name="allowChatHelp">Naj igralci razpravljajo v klepetalnici</string>
|
||||
<string name="arenaStreaks">Arena proge</string>
|
||||
<string name="arenaStreaksHelp">Po 2 zaporednih zmagah nadaljne zmage namesto 2 dodelijo 4 točke.</string>
|
||||
</resources>
|
||||
|
|
|
@ -42,4 +42,12 @@ Daha fazla oyun oynayıp daha çok puan kazanmak için hızlıca oynayın ve lob
|
|||
<item quantity="one">Takımı görüntüleyin</item>
|
||||
<item quantity="other">Tüm %s takımlarını görüntüleyin</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Yeni Takım Çarpışması</string>
|
||||
<string name="customStartDate">Özel başlangıç tarihi</string>
|
||||
<string name="customStartDateHelp">Kendi saat diliminizi kullanın. \"Turnuvaya kalan süre\" ayarı geçersiz kalacaktır</string>
|
||||
<string name="allowBerserk">Divane moduna izin ver</string>
|
||||
<string name="allowBerserkHelp">Ekstra puan kazanmak isteyen oyuncuların, sürelerinin yarısından feragat etmesine izin ver</string>
|
||||
<string name="allowChatHelp">Oyuncuların sohbet odasında konuşmasına izin ver</string>
|
||||
<string name="arenaStreaks">Arena serisi</string>
|
||||
<string name="arenaStreaksHelp">2 kez üst üste kazandıktan sonra yapılan maçlar 2 yerine 4 puan verir.</string>
|
||||
</resources>
|
||||
|
|
|
@ -46,4 +46,12 @@
|
|||
<item quantity="many">Переглянути %s команд</item>
|
||||
<item quantity="other">Переглянути %s команди</item>
|
||||
</plurals>
|
||||
<string name="newTeamBattle">Нова Командна битва</string>
|
||||
<string name="customStartDate">Власна дата початку</string>
|
||||
<string name="customStartDateHelp">У вашому локальному часовому поясі. Це замінює налаштування \"Час до початку турніру\"</string>
|
||||
<string name="allowBerserk">Дозволити Берсерк</string>
|
||||
<string name="allowBerserkHelp">Дозволити гравцям зменшити наполовину свій час, щоб отримати додатковий бал</string>
|
||||
<string name="allowChatHelp">Дозволити гравцям обговорення в чаті</string>
|
||||
<string name="arenaStreaks">Серії Арени</string>
|
||||
<string name="arenaStreaksHelp">Після двох перемог, кожна наступна перемога принесе 4 бали замість 2.</string>
|
||||
</resources>
|
||||
|
|
|
@ -36,8 +36,7 @@
|
|||
</plurals>
|
||||
<string name="thisIsPrivate">這是一個非公開的錦標賽</string>
|
||||
<string name="shareUrl">分享這個網址讓其他人加入這場錦標賽:%s</string>
|
||||
<string name="drawStreak">連續平手機制:當一位玩家連續在錦標賽中或的平局,只有第一場平局會獲得積分,或是在下超過%s步時才會獲得積分。
|
||||
連續平手機制只會在獲勝或輸的時候被解除。</string>
|
||||
<string name="drawStreak">連續平手機制:當一位玩家連續在錦標賽中或的平局,只有第一場平局會獲得積分,或是在下超過%s步時才會獲得積分。連續平手機制只會在獲勝或輸的時候被解除。</string>
|
||||
<string name="history">先前的重要錦標賽</string>
|
||||
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other] %s is the number of teams that can be viewed">
|
||||
<item quantity="other">查看所有 %s 團隊</item>
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
<string name="broadcasts">Трансляція</string>
|
||||
<string name="liveBroadcasts">Онлайн трансляції турнірів</string>
|
||||
<string name="newBroadcast">Нова трансляція наживо</string>
|
||||
<string name="addRound">Додати раунд</string>
|
||||
<string name="ongoing">Поточні</string>
|
||||
<string name="upcoming">Майбутні</string>
|
||||
<string name="completed">Завершені</string>
|
||||
<string name="roundNumber">Номер раунда</string>
|
||||
<string name="roundName">Назва раунду</string>
|
||||
<string name="roundNumber">Номер раунду</string>
|
||||
<string name="tournamentName">Назва турніру</string>
|
||||
<string name="tournamentDescription">Короткий опис турніру</string>
|
||||
<string name="fullDescription">Повний опис події</string>
|
||||
<string name="fullDescriptionHelp">Необов\'язковий довгий опис трансляції. Наявна розмітка %1$s. Довжина має бути менша ніж %2$s символів.</string>
|
||||
<string name="sourceUrlOrGameIds">URL джерела, або ідентифікатор гри</string>
|
||||
|
@ -15,4 +19,9 @@
|
|||
<string name="startDate">Дата початку у Вашому часовому поясі</string>
|
||||
<string name="startDateHelp">Необов\'язково, якщо Ви знаєте, коли починається подія</string>
|
||||
<string name="credits">Вдячність джерелу</string>
|
||||
<string name="resetRound">Скинути цей раунд</string>
|
||||
<string name="deleteRound">Видалити цей раунд</string>
|
||||
<string name="broadcastUrl">Посилання на трансляцію</string>
|
||||
<string name="currentRoundUrl">Посилання на поточний раунд</string>
|
||||
<string name="currentGameUrl">Посилання на поточну гру</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="challenges">Usäfoderigä</string>
|
||||
<string name="challengeToPlay">Zu nere partie useforderä</string>
|
||||
<string name="challengeToPlay">Zunärä Partii usäfordärä</string>
|
||||
<string name="challengeDeclined">Usäfoderig abglähnt</string>
|
||||
<string name="challengeAccepted">Usäfoderig angnoo!</string>
|
||||
<string name="challengeCanceled">Usäfoderig abbrochää.</string>
|
||||
|
@ -13,11 +13,11 @@
|
|||
<string name="xOnlyAcceptsChallengesFromFriends">%s nimmt Usäfoderigä nur vo Brudis a.</string>
|
||||
<string name="declineGeneric">I nim momentan kei Usäfoderigä me a.</string>
|
||||
<string name="declineLater">I nim momentan kei Usäfoderigä me a, bitte frog spöter nonemol.</string>
|
||||
<string name="declineTooFast">Ziet zum überlege isch z\'churz für mi, bitte forderä mich nomel mit meh Bedenkzeit ussä.</string>
|
||||
<string name="declineTooSlow">Ziet zum überlege isch z\'lang für mi, bitte forderä mich nomel mit weniger Bedenkzeit ussä.</string>
|
||||
<string name="declineTooFast">Ziit zum Überlegä isch z\'churz für mich, bitte fordärä mich nomol mit meh Bedänkziit usä.</string>
|
||||
<string name="declineTooSlow">Ziit zum Überlegä isch z\'lang für mich, bitte fordärä mich nomol mit wäniger Bedänkziit usä.</string>
|
||||
<string name="declineTimeControl">I nim momentan kei Usäfoderigä me a.</string>
|
||||
<string name="declineRated">Bitte forderä mi stattdessä zu enerä gewertetä Partie usä.</string>
|
||||
<string name="declineCasual">Bitte fordere mich stattdessä zu einer ungewertetä Partie usä.</string>
|
||||
<string name="declineRated">Bitte fordrää mich schtattdessä zunärä gwärtätä Partii usä.</string>
|
||||
<string name="declineCasual">Bitte fordärä mich schtattdessä zunärä ungwärtätä Partii usä.</string>
|
||||
<string name="declineStandard">I nimm momentan kei Usäforderigä für anderi Schpiilvariantä a.</string>
|
||||
<string name="declineVariant">I bi im moment nöd parrat, diä Variantä zu spielä.</string>
|
||||
<string name="declineNoBot">Ich nimm kei Usäfoderigä von Bots a.</string>
|
||||
|
|
|
@ -1,2 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="challenges">Defioj</string>
|
||||
<string name="challengeToPlay">Defii al nova ludo</string>
|
||||
<string name="challengeDeclined">Defio malakceptita</string>
|
||||
<string name="challengeAccepted">Defio akceptita!</string>
|
||||
<string name="challengeCanceled">Defio nuligita.</string>
|
||||
<string name="youCannotChallengeX">Vi ne povas defii %s.</string>
|
||||
<string name="xDoesNotAcceptChallenges">%s ne akceptas defiojn.</string>
|
||||
<string name="xOnlyAcceptsChallengesFromFriends">%s nur akceptas defiojn de amikoj.</string>
|
||||
<string name="declineGeneric">Mi ne akceptas defiojn en tiu ĉi momento.</string>
|
||||
<string name="declineNoBot">Mi ne akceptas defiojn de robotoj.</string>
|
||||
<string name="declineOnlyBot">Mi nur akceptas defiojn de robotoj.</string>
|
||||
</resources>
|
||||
|
|
|
@ -8,5 +8,18 @@
|
|||
<string name="registerToSendChallenges">אנא הירשמו על מנת לשלוח הזמנות למשחקים.</string>
|
||||
<string name="youCannotChallengeX">לא ניתן להזמין את %s למשחק.</string>
|
||||
<string name="xDoesNotAcceptChallenges">%s לא מקבל הזמנות למשחקים.</string>
|
||||
<string name="yourXRatingIsTooFarFromY">הדירוג שלך %1$s רחוק מדי מ%2$s.</string>
|
||||
<string name="cannotChallengeDueToProvisionalXRating">אין אפשרות לאתגר בגלל דירוג זמני של %s.</string>
|
||||
<string name="xOnlyAcceptsChallengesFromFriends">%s מאשר רק אתגרים מחברים.</string>
|
||||
<string name="declineGeneric">לא מקבל תלמידים בזמן זה.</string>
|
||||
<string name="declineLater">זה לא הזמן המתאים עבורי, אנא שאל שוב מאוחר יותר.</string>
|
||||
<string name="declineTooFast">הזמנים האלו יותר מדי מהירים בשבילי, בבקשה תאתגר שוב עם משחק יותר איטי.</string>
|
||||
<string name="declineTooSlow">הזמנים האלו יותר מדי איטיים בשבילי, בבקשה תאתגר שוב עם משחק יותר מהיר.</string>
|
||||
<string name="declineTimeControl">אני לא מאשר אתגרים עם בקרת זמנים.</string>
|
||||
<string name="declineRated">בבקשה תשלח לי אתגר מדורג במקום.</string>
|
||||
<string name="declineCasual">בבקשה תשלח לי אתגר לא מדורג במקום.</string>
|
||||
<string name="declineStandard">אני לא מקבל אתגרים של גרסאות אחרות בזמן זה.</string>
|
||||
<string name="declineVariant">אני לא מוכן לשחק את הגרסה האחרת הזאת עכשיו.</string>
|
||||
<string name="declineNoBot">אני לא מאשר הזמנות מבוטים.</string>
|
||||
<string name="declineOnlyBot">מאשר הזמנות למשחק רק מבוטים.</string>
|
||||
</resources>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue