Skip to content

Commit

Permalink
Merge branch 'master' into plus-normalization-emails
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar authored Jan 25, 2025
2 parents 0b73e49 + 5eccf2e commit caba7e4
Show file tree
Hide file tree
Showing 404 changed files with 3,282 additions and 3,228 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/flair.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ name: Validate Flair
on:
push:
paths:
- '.github/workflows/flair.yml'
- 'public/flair/**'
pull_request:
paths:
- '.github/workflows/flair.yml'
- 'public/flair/**'

jobs:
validate-flair:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y imagemagick
- run: ./bin/validate-flair public/flair/img/
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.5"
version = "3.8.6"
runner.dialect = scala3

align.preset = more
Expand Down
1 change: 1 addition & 0 deletions COPYING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public/sounds/piano | [Enigmahack](https://github.com/Enigmahack) | AGPLv3+
public/sounds/sfx | [Enigmahack](https://github.com/Enigmahack) | AGPLv3+
public/sounds/lisp | [EdinburghCollective](http://lichess.org/@/EdinburghCollective) | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
SVG in ui/learn/src/apple.ts | [Sensa](https://www.svgrepo.com/svg/434273/star) | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/deed.en)
public/flair/img/symbols.neovim-mark.webp | [Jason Long](https://twitter.com/jasonlong) | [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) (Modified by converting to webp and resizing)

## Exceptions (non-free)

Expand Down
3 changes: 2 additions & 1 deletion app/LilaComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ final class LilaComponents(
import play.api.libs.ws.WSConfigParser
import play.api.libs.ws.ahc.{ AhcConfigBuilder, AhcWSClientConfigParser, StandaloneAhcWSClient }
new StandaloneAhcWSClient(
DefaultAsyncHttpClient:
DefaultAsyncHttpClient(
AhcConfigBuilder(
AhcWSClientConfigParser(
WSConfigParser(configuration.underlying, environment.classLoader).parse(),
configuration.underlying,
environment.classLoader
).parse()
).modifyUnderlying(_.setIoThreadsCount(8)).build()
)
)

val env: lila.app.Env =
Expand Down
8 changes: 3 additions & 5 deletions app/controllers/Account.scala
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,9 @@ final class Account(
NotManaged:
auth.HasherRateLimit:
env.security.forms.closeAccount.flatMap: form =>
FormFuResult(form)(err => renderPage(pages.close(err, managed = false))): _ =>
env.api.accountTermination
.disable(me.value)
.inject:
Redirect(routes.User.show(me.username)).withCookies(env.security.lilaCookie.newSession)
FormFuResult(form)(err => renderPage(pages.close(err, managed = false))): forever =>
for _ <- env.api.accountTermination.disable(me.value, forever = forever)
yield Redirect(routes.Lobby.home).withCookies(env.security.lilaCookie.newSession)
}

def delete = Auth { _ ?=> me ?=>
Expand Down
33 changes: 16 additions & 17 deletions app/controllers/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,22 +167,21 @@ final class Api(
}
.map(toApiResult)

def tournamentGames(id: TourId) =
AnonOrScoped(): ctx ?=>
env.tournament.tournamentRepo.byId(id).orNotFound { tour =>
val onlyUserId = getUserStr("player").map(_.id)
val config = GameApiV2.ByTournamentConfig(
tour = tour,
format = GameApiV2.Format.byRequest(ctx.req),
flags = gameC.requestPgnFlags(extended = false),
perSecond = gamesPerSecond(ctx.me)
)
GlobalConcurrencyLimitPerIP
.download(ctx.req.ipAddress)(env.api.gameApiV2.exportByTournament(config, onlyUserId)): source =>
Ok.chunked(source)
.pipe(asAttachmentStream(env.api.gameApiV2.filename(tour, config.format)))
.as(gameC.gameContentType(config))
}
def tournamentGames(id: TourId) = AnonOrScoped(): ctx ?=>
env.tournament.tournamentRepo.byId(id).orNotFound { tour =>
val onlyUserId = getUserStr("player").map(_.id)
val config = GameApiV2.ByTournamentConfig(
tour = tour,
format = GameApiV2.Format.byRequest,
flags = gameC.requestPgnFlags(extended = false),
perSecond = gamesPerSecond(ctx.me)
)
GlobalConcurrencyLimitPerIP
.download(ctx.req.ipAddress)(env.api.gameApiV2.exportByTournament(config, onlyUserId)): source =>
Ok.chunked(source)
.pipe(asAttachmentStream(env.api.gameApiV2.filename(tour, config.format)))
.as(gameC.gameContentType(config))
}

def tournamentResults(id: TourId) = Anon:
val csv = HTTPRequest.acceptsCsv(req) || get("as").has("csv")
Expand Down Expand Up @@ -220,7 +219,7 @@ final class Api(
Found(env.swiss.cache.swissCache.byId(id)): swiss =>
val config = GameApiV2.BySwissConfig(
swissId = swiss.id,
format = GameApiV2.Format.byRequest(req),
format = GameApiV2.Format.byRequest,
flags = gameC.requestPgnFlags(extended = false),
perSecond = gamesPerSecond(ctx.me),
player = getUserStr("player").map(_.id)
Expand Down
6 changes: 2 additions & 4 deletions app/controllers/BulkPairing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ final class BulkPairing(gameC: => Game, apiC: => Api, env: Env) extends LilaCont
_.fold(notFoundText()): bulk =>
val config = GameApiV2.ByIdsConfig(
ids = bulk.games.map(_.id),
format = GameApiV2.Format.byRequest(req),
flags = gameC
.requestPgnFlags(extended = false)
.copy(delayMoves = false),
format = GameApiV2.Format.byRequest,
flags = gameC.requestPgnFlags(extended = false).copy(delayMoves = false),
perSecond = MaxPerSecond(50)
)
apiC.GlobalConcurrencyLimitPerIP
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Challenge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ final class Challenge(env: Env) extends LilaController(env):
}
}
.map { cookieOption =>
cookieOption.foldLeft(res)(_ withCookies _)
cookieOption.foldLeft(res)(_.withCookies(_))
}

def decline(id: ChallengeId) = AuthBody { ctx ?=> _ ?=>
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Clas.scala
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
WithStudent(clas, username): s =>
if s.student.managed then
(env.clas.api.student.closeAccount(s) >>
env.api.accountTermination.disable(s.user)).inject(redirectTo(clas).flashSuccess)
env.api.accountTermination.disable(s.user, forever = false)).inject(redirectTo(clas).flashSuccess)
else if s.student.isArchived then
env.clas.api.student.closeAccount(s) >>
redirectTo(clas).flashSuccess
Expand Down
30 changes: 27 additions & 3 deletions app/controllers/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env):
private[controllers] def exportGame(gameId: GameId)(using Context): Fu[Result] =
Found(env.round.proxyRepo.gameIfPresent(gameId).orElse(env.game.gameRepo.game(gameId))): game =>
val config = GameApiV2.OneConfig(
format = if HTTPRequest.acceptsJson(req) then GameApiV2.Format.JSON else GameApiV2.Format.PGN,
format = GameApiV2.Format.byRequest,
imported = getBool("imported"),
flags = requestPgnFlags(extended = true),
playerFile = get("players")
Expand All @@ -54,7 +54,7 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env):
private def handleExport(username: UserStr)(using ctx: Context) =
meOrFetch(username).flatMap:
_.filter(u => u.enabled.yes || ctx.is(u) || isGrantedOpt(_.GamesModView)).so: user =>
val format = GameApiV2.Format.byRequest(req)
val format = GameApiV2.Format.byRequest
WithVs: vs =>
env.security.ipTrust
.throttle(MaxPerSecond:
Expand Down Expand Up @@ -110,11 +110,35 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env):
.as(pgnContentType)
}

def apiExportByUserBookmarks() = Scoped() { ctx ?=> me ?=>
val config = GameApiV2.BookmarkConfig(
user = me.userId,
format = GameApiV2.Format.byRequest,
since = getTimestamp("since"),
until = getTimestamp("until"),
max = getIntAs[Max]("max").map(_.atLeast(1)),
flags = requestPgnFlags(extended = false),
sort =
if get("sort").has("dateAsc") then GameApiV2.GameSort.DateAsc
else GameApiV2.GameSort.DateDesc,
perSecond = MaxPerSecond(30)
)
apiC.GlobalConcurrencyLimitPerIpAndUserOption(me.some)(
env.api.gameApiV2.exportUserBookmarks(config)
): source =>
Ok.chunked(source)
.pipe:
asAttachmentStream(
s"lichess_${me.username}_$fileDate.bookmarks.${config.format.toString.toLowerCase}"
)
.as(gameContentType(config))
}

def exportByIds = AnonOrScopedBody(parse.tolerantText)(): ctx ?=>
val (limit, perSec) = if ctx.me.exists(_.isVerifiedOrChallengeAdmin) then (600, 100) else (300, 30)
val config = GameApiV2.ByIdsConfig(
ids = GameId.from(ctx.body.body.split(',').view.take(limit).toSeq),
format = GameApiV2.Format.byRequest(req),
format = GameApiV2.Format.byRequest,
flags = requestPgnFlags(extended = false),
perSecond = MaxPerSecond(perSec),
playerFile = get("players")
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,7 @@ final class Main(
env.memo.picfitApi.bodyImage
.upload(rel, image)
.map(url => JsonOk(Json.obj("imageUrl" -> url)))
.recover:
case e: Exception => JsonBadRequest(jsonError(e.getMessage))
case None => JsonBadRequest(jsonError("Image content only"))
}
10 changes: 6 additions & 4 deletions app/controllers/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class Mod(
withSuspect(username): sus =>
for
_ <- api.setAlt(sus, v)
_ <- (v && sus.user.enabled.yes).so(env.api.accountTermination.disable(sus.user))
_ <- (v && sus.user.enabled.yes).so(env.api.accountTermination.disable(sus.user, forever = false))
_ <- (!v && sus.user.enabled.no).so(api.reopenAccount(sus.user.id))
yield sus.some
}(reportC.onModAction)
Expand All @@ -40,7 +40,9 @@ final class Mod(
Source(ctx.body.body.split(' ').toList.flatMap(UserStr.read))
.mapAsync(1): username =>
withSuspect(username): sus =>
api.setAlt(sus, true) >> (sus.user.enabled.yes.so(env.api.accountTermination.disable(sus.user)))
api.setAlt(sus, true) >> (sus.user.enabled.yes.so(
env.api.accountTermination.disable(sus.user, forever = false)
))
.runWith(Sink.ignore)
.void
.inject(NoContent)
Expand Down Expand Up @@ -114,7 +116,7 @@ final class Mod(

def closeAccount(username: UserStr) = OAuthMod(_.CloseAccount) { _ ?=> me ?=>
meOrFetch(username).flatMapz: user =>
env.api.accountTermination.disable(user).map(some)
env.api.accountTermination.disable(user, forever = false).map(some)
}(actionResult(username))

def reopenAccount(username: UserStr) = OAuthMod(_.CloseAccount) { _ ?=> me ?=>
Expand Down Expand Up @@ -482,7 +484,7 @@ final class Mod(
for
_ <- (!user.everLoggedIn).so {
lila.mon.user.register.modConfirmEmail.increment()
api.setEmail(user.id, setEmail)
api.setEmail(user.id, setEmail.some)
}
email <- env.user.repo.email(user.id)
page <- renderPage(views.mod.ui.emailConfirm("", user.some, email))
Expand Down
69 changes: 34 additions & 35 deletions app/controllers/Round.scala
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ final class Round(
private[controllers] def getWatcherChat(
game: GameModel
)(using ctx: Context): Fu[Option[lila.chat.UserChat.Mine]] = {
ctx.kid.no && (ctx.noBot || ctx.userId.exists(game.userIds.has)) && ctx.me.fold(
(ctx.noBot || ctx.userId.exists(game.userIds.has)) && ctx.me.fold(
HTTPRequest.isHuman(ctx.req)
)(env.chat.panic.allowed(_)) && {
game.finishedOrAborted || !ctx.userId.exists(game.userIds.has)
Expand All @@ -211,41 +211,40 @@ final class Round(
private[controllers] def getPlayerChat(game: GameModel, tour: Option[Tour])(using
ctx: Context
): Fu[Option[Chat.GameOrEvent]] =
ctx.kid.no.so:
def toEventChat(resource: String)(c: lila.chat.UserChat.Mine) =
Chat
.GameOrEvent:
Right:
(c.truncate(100), lila.chat.Chat.ResourceId(resource))
.some
(game.tournamentId, game.simulId, game.swissId) match
case (Some(tid), _, _) =>
val hasChat = ctx.isAuth && tour.forall(tournamentC.canHaveChat(_, none))
hasChat.so(
def toEventChat(resource: String)(c: lila.chat.UserChat.Mine) =
Chat
.GameOrEvent:
Right:
(c.truncate(100), lila.chat.Chat.ResourceId(resource))
.some
(game.tournamentId, game.simulId, game.swissId) match
case (Some(tid), _, _) =>
val hasChat = ctx.isAuth && tour.forall(tournamentC.canHaveChat(_, none))
hasChat.so(
env.chat.api.userChat.cached
.findMine(tid.into(ChatId))
.dmap(toEventChat(s"tournament/$tid"))
)
case (_, Some(sid), _) =>
env.chat.api.userChat.cached.findMine(sid.into(ChatId)).dmap(toEventChat(s"simul/$sid"))
case (_, _, Some(sid)) =>
env.swiss.api
.roundInfo(sid)
.flatMapz(swissC.canHaveChat)
.flatMapz:
env.chat.api.userChat.cached
.findMine(tid.into(ChatId))
.dmap(toEventChat(s"tournament/$tid"))
)
case (_, Some(sid), _) =>
env.chat.api.userChat.cached.findMine(sid.into(ChatId)).dmap(toEventChat(s"simul/$sid"))
case (_, _, Some(sid)) =>
env.swiss.api
.roundInfo(sid)
.flatMapz(swissC.canHaveChat)
.flatMapz:
env.chat.api.userChat.cached
.findMine(sid.into(ChatId))
.dmap(toEventChat(s"swiss/$sid"))
case _ =>
game.hasChat.so:
for
chat <- env.chat.api.playerChat.findIf(game.id.into(ChatId), !game.justCreated)
lines <- lila.chat.JsonView.asyncLines(chat)
yield Chat
.GameOrEvent:
Left:
Chat.Restricted(chat, lines, restricted = game.sourceIs(_.Lobby) && ctx.isAnon)
.some
.findMine(sid.into(ChatId))
.dmap(toEventChat(s"swiss/$sid"))
case _ =>
game.hasChat.so:
for
chat <- env.chat.api.playerChat.findIf(game.id.into(ChatId), !game.justCreated)
lines <- lila.chat.JsonView.asyncLines(chat)
yield Chat
.GameOrEvent:
Left:
Chat.Restricted(chat, lines, restricted = game.sourceIs(_.Lobby) && ctx.isAnon)
.some

def sides(gameId: GameId, color: Color) = Open:
FoundSnip(env.round.proxyRepo.pov(gameId, color)): pov =>
Expand Down
8 changes: 4 additions & 4 deletions app/controllers/Simul.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ final class Simul(env: Env) extends LilaController(env):
NoLameOrBot:
Ok.async:
env.team.api
.lightsByTourLeader(me)
.lightsOf(me)
.map: teams =>
views.simul.form.create(forms.create(teams), teams)
}

def create = AuthBody { ctx ?=> me ?=>
NoLameOrBot:
env.team.api
.lightsByTourLeader(me)
.lightsOf(me)
.flatMap: teams =>
bindForm(forms.create(teams))(
err => BadRequest.page(views.simul.form.create(err, teams)),
Expand Down Expand Up @@ -138,15 +138,15 @@ final class Simul(env: Env) extends LilaController(env):
def edit(id: SimulId) = Auth { ctx ?=> me ?=>
AsHost(id): simul =>
Ok.async:
env.team.api.lightsByTourLeader(me).map { teams =>
env.team.api.lightsOf(me).map { teams =>
views.simul.form.edit(forms.edit(teams, simul), teams, simul)
}
}

def update(id: SimulId) = AuthBody { ctx ?=> me ?=>
AsHost(id): simul =>
env.team.api
.lightsByTourLeader(me)
.lightsOf(me)
.flatMap: teams =>
def errPage(err: lila.simul.SimulForm.EitherForm) =
BadRequest.page(views.simul.form.edit(err, teams, simul))
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Tv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ final class Tv(env: Env, apiC: => Api, gameC: => Game) extends LilaController(en
val config =
lila.api.GameApiV2.ByIdsConfig(
ids = gameIds,
format = lila.api.GameApiV2.Format.byRequest(req),
format = lila.api.GameApiV2.Format.byRequest,
flags = gameC.requestPgnFlags(extended = false).copy(delayMoves = false),
perSecond = MaxPerSecond(30)
)
Expand Down
13 changes: 2 additions & 11 deletions bin/gen/generate_css_for_a_board_with_backgroundimages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,13 @@

#config
schemeName = "canvas"
bgImgWhite = "../images/canvasboard_white.jpg"
bgImgBlack = "../images/canvasboard_black.jpg"

# adv. config
squareSize = 64 # width of a square in pixel
squareSize = 64 # width of a square in pixels

#code
print("body." + schemeName + " #GameBoard td.whiteSquare, body." + schemeName + " #GameBoard td.highlightWhiteSquare, body." + schemeName + " div.lcs.white, #top div.lcs.white." + schemeName + ", body." + schemeName + " div.lichess_board { background: url(../images/woodenboard_white.jpg) no-repeat; }")
print("body." + schemeName + " #GameBoard td.blackSquare, body." + schemeName + " #GameBoard td.highlightBlackSquare, body." + schemeName + " div.lcs.black, #top div.lcs.black." + schemeName + " { background: url(../images/woodenboard_black.jpg) no-repeat; }")
white = True
for y in range(0,8):
for x in range (0, 8):
if white:
img = bgImgWhite
else:
img = bgImgBlack
for x in range (0,8):
print("body." + schemeName + " #tcol"+str(x)+"trow"+str(y)+", body." + schemeName + " #"+str(chr(ord('a') + x))+str(8-y) + " { background-position: "+str((-x)*squareSize)+"px " +str((-y)*squareSize) +"px; }")
white = not white
white = not white
Loading

0 comments on commit caba7e4

Please sign in to comment.