Skip to content

Commit

Permalink
Merge branch 'master' into language-mapping-revamp
Browse files Browse the repository at this point in the history
  • Loading branch information
superuser-does authored Dec 28, 2024
2 parents 5864551 + c811034 commit 4ddfb03
Show file tree
Hide file tree
Showing 55 changed files with 358 additions and 150 deletions.
2 changes: 1 addition & 1 deletion app/controllers/Study.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import lila.core.study.Order
import lila.study.JsonView.JsData
import lila.study.PgnDump.WithFlags
import lila.study.Study.WithChapter
import lila.study.actorApi.{ BecomeStudyAdmin, Who }
import lila.study.{ BecomeStudyAdmin, Who }
import lila.study.{ Chapter, Orders, Settings, Study as StudyModel, StudyForm }
import lila.tree.Node.partitionTreeJsonWriter
import com.fasterxml.jackson.core.JsonParseException
Expand Down
2 changes: 1 addition & 1 deletion modules/common/src/main/mon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ object mon:
timer("relay.sync.time").withTags(relay(official, id, slug))
def httpGet(code: Int, host: String, etag: String, proxy: Option[String]) =
timer("relay.http.get").withTags:
tags("code" -> code, "host" -> host, "etag" -> etag, "proxy" -> proxy.getOrElse("none"))
tags("code" -> code.toLong, "host" -> host, "etag" -> etag, "proxy" -> proxy.getOrElse("none"))
val dedup = counter("relay.fetch.dedup").withoutTags()

object bot:
Expand Down
2 changes: 1 addition & 1 deletion modules/practice/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ final class Env(

def getStudies: lila.core.practice.GetStudies = api.structure.getStudies

lila.common.Bus.subscribeFun("study") { case lila.study.actorApi.SaveStudy(study) =>
lila.common.Bus.subscribeFun("study") { case lila.study.SaveStudy(study) =>
api.structure.onSave(study)
}
8 changes: 4 additions & 4 deletions modules/relay/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,19 @@ final class Env(
"study" -> { case lila.core.study.RemoveStudy(studyId) =>
api.onStudyRemove(studyId)
},
"relayToggle" -> { case lila.study.actorApi.RelayToggle(id, v, who) =>
"relayToggle" -> { case lila.study.RelayToggle(id, v, who) =>
studyApi
.isContributor(id, who.u)
.foreach:
_.so(api.requestPlay(id.into(RelayRoundId), v, "manual toggle"))
},
"kickStudy" -> { case lila.study.actorApi.Kick(studyId, userId, who) =>
"kickStudy" -> { case lila.study.Kick(studyId, userId, who) =>
roundRepo.tourIdByStudyId(studyId).flatMapz(api.kickBroadcast(userId, _, who))
},
"adminStudy" -> { case lila.study.actorApi.BecomeStudyAdmin(studyId, me) =>
"adminStudy" -> { case lila.study.BecomeStudyAdmin(studyId, me) =>
api.becomeStudyAdmin(studyId, me)
},
"isOfficialRelay" -> { case lila.study.actorApi.IsOfficialRelay(studyId, promise) =>
"isOfficialRelay" -> { case lila.study.IsOfficialRelay(studyId, promise) =>
promise.completeWith(api.isOfficial(studyId.into(RelayRoundId)))
}
)
Expand Down
4 changes: 2 additions & 2 deletions modules/relay/src/main/RelayDelay.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final private class RelayDelay(colls: RelayColls)(using Executor):
): Fu[RelayGames] =
dedupCache(url, round, () => doFetchUrl(url))
.flatMap: latest =>
round.sync.delay match
round.sync.delayMinusLag match
case Some(delay) if delay > 0 => store.get(url, delay).map(_ | latest.map(_.resetToSetup))
case _ => fuccess(latest)

Expand Down Expand Up @@ -48,7 +48,7 @@ final private class RelayDelay(colls: RelayColls)(using Executor):
)
.games
.addEffect: games =>
if round.sync.hasDelay then store.putIfNew(url, games)
if round.sync.delayMinusLag.isDefined then store.putIfNew(url, games)

private object store:

Expand Down
20 changes: 12 additions & 8 deletions modules/relay/src/main/RelayFetch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,9 @@ final private class RelayFetch(
private def continueRelay(tour: RelayTour, updating: Updating[RelayRound]): Updating[RelayRound] =
val round = updating.current
round.sync.upstream.fold(updating): upstream =>
reportBroadcastFailure(round.withTour(tour))
val seconds: Seconds =
if round.sync.log.alwaysFails then
round.sync.log.events.lastOption
.filterNot(_.isTimeout)
.flatMap(_.error)
.ifTrue(tour.official && round.shouldHaveStarted)
.filterNot(_.contains("Cannot parse move"))
.filterNot(_.contains("Cannot parse pgn"))
.filterNot(_.contains("Found an empty PGN"))
.foreach { irc.broadcastError(round.id, round.withTour(tour).fullName, _) }
Seconds(tour.tier.fold(60):
case RelayTour.Tier.best => 10
case RelayTour.Tier.high => 20
Expand All @@ -173,6 +166,17 @@ final private class RelayFetch(
}.some
)

private def reportBroadcastFailure(r: RelayRound.WithTour): Unit =
if r.round.sync.log.alwaysFails then
r.round.sync.log.events.lastOption
.filterNot(_.isTimeout)
.flatMap(_.error)
.ifTrue(r.tour.official && r.round.shouldHaveStarted)
.filterNot(_.contains("Cannot parse move"))
.filterNot(_.contains("Cannot parse pgn"))
.filterNot(_.contains("Found an empty PGN"))
.foreach { irc.broadcastError(r.round.id, r.fullName, _) }

private def dynamicPeriod(tour: RelayTour, round: RelayRound, upstream: Sync.Upstream) = Seconds:
val base =
if upstream.hasLcc then 5
Expand Down
2 changes: 1 addition & 1 deletion modules/relay/src/main/RelayPlayerEnrich.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,6 @@ private final class RelayPlayerEnrich(
chapterId = chapter.id,
tags = enriched,
newName = newName.filter(_ != chapter.name)
)(lila.study.actorApi.Who(chapter.ownerId, Sri("")))
)(lila.study.Who(chapter.ownerId, Sri("")))
.runWith(Sink.ignore)
yield ()
2 changes: 1 addition & 1 deletion modules/relay/src/main/RelayPush.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class RelayPush(
parsed.map(_.map(g => Success(g.tags, g.root.mainline.size)))
val andSyncTargets = response.exists(_.isRight)

rt.round.sync.nonEmptyDelay
rt.round.sync.delayMinusLag
.ifTrue(games.exists(_.root.children.nonEmpty))
.match
case None => push(rt, games, andSyncTargets).inject(response)
Expand Down
5 changes: 3 additions & 2 deletions modules/relay/src/main/RelayRound.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ object RelayRound:
def addLog(event: SyncLog.Event) = copy(log = log.add(event))
def clearLog = copy(log = SyncLog.empty)

def nonEmptyDelay = delay.filter(_.value > 0)
def hasDelay = nonEmptyDelay.isDefined
// subtract estimated source polling lag from transmission delay
private def pollingLag = Seconds(if isPush then 1 else 6)
def delayMinusLag = delay.map(_ - pollingLag).filter(_.value > 0)

override def toString = upstream.toString

Expand Down
37 changes: 19 additions & 18 deletions modules/relay/src/main/RelaySync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import chess.format.pgn.{ Tag, Tags }
import lila.core.socket.Sri
import lila.study.*
import lila.tree.Branch
import lila.study.AddNode

final private class RelaySync(
studyApi: StudyApi,
Expand Down Expand Up @@ -91,20 +92,19 @@ final private class RelaySync(
studyId = study.id,
position = Position(chapter, path).ref,
toMainline = true
)(by) >> chapterRepo.setRelayPath(chapter.id, path)
)(using by) >> chapterRepo.setRelayPath(chapter.id, path)
_ <- newNode match
case Some(newNode) =>
newNode.mainline
.foldM(Position(chapter, path).ref): (position, n) =>
studyApi
.addNode(
studyId = study.id,
position = position,
node = n,
opts = moveOpts,
relay = makeRelayFor(game, position.path + n.id).some
)(by)
.inject(position + n)
val node = AddNode(
studyId = study.id,
positionRef = position,
node = n,
opts = moveOpts,
relay = makeRelayFor(game, position.path + n.id).some
)(using by)
studyApi.addNode(node).inject(position + n)
case None =>
// the chapter already has all the game moves,
// but its relayPath might be out of sync. This can happen if the broadcast
Expand All @@ -121,13 +121,14 @@ final private class RelaySync(
game.root.children
.nodeAt(gameMainlinePath)
.so: lastMainlineNode =>
studyApi.addNode(
studyId = study.id,
position = Position(chapter, gameMainlinePath.parent).ref,
node = lastMainlineNode,
opts = moveOpts,
relay = makeRelayFor(game, gameMainlinePath).some
)(by)
studyApi.addNode:
AddNode(
studyId = study.id,
positionRef = Position(chapter, gameMainlinePath.parent).ref,
node = lastMainlineNode,
opts = moveOpts,
relay = makeRelayFor(game, gameMainlinePath).some
)(using by)
yield newNode.so(_.mainline.size)

private def updateChapterTags(
Expand Down Expand Up @@ -212,7 +213,7 @@ final private class RelaySync(
)

private val sri = Sri("")
private def who(userId: UserId) = actorApi.Who(userId, sri)
private def who(userId: UserId) = Who(userId, sri)

private def vs(tags: Tags) = s"${tags(_.White) | "?"} - ${tags(_.Black) | "?"}"

Expand Down
4 changes: 2 additions & 2 deletions modules/relay/src/main/ui/RelayTourUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
nonEmptyTier(_.high),
nonEmptyTier(_.normal),
h2(cls := "relay-index__section")(trc.pastBroadcasts()),
div(cls := "relay-cards relay-cards--past"):
div(cls := "relay-cards"):
past.map: t =>
card.render(t, live = _ => false)
,
Expand Down Expand Up @@ -150,7 +150,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
div(cls := "page-menu__content box box-pad")(
boxTop(h1(dataIcon := Icon.RadioTower, cls := "text")(trc.broadcastCalendar())),
dateForm("top"),
div(cls := "relay-cards relay-cards--past"):
div(cls := "relay-cards"):
tours.map(card.renderCalendar)
,
(tours.sizeIs > 8).option(dateForm("bottom"))
Expand Down
19 changes: 13 additions & 6 deletions modules/security/src/main/EmailAddressValidator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ final class EmailAddressValidator(

// only compute valid and non-whitelisted email domains
private[security] def apply(e: EmailAddress): Fu[Result] =
e.domain.map(_.lower).fold(fuccess(Result.DomainMissing))(validateDomain)
if isInfiniteAlias(e) then fuccess(Result.Alias)
else e.domain.map(_.lower).fold(fuccess(Result.DomainMissing))(validateDomain)

private[security] def validateDomain(domain: Domain.Lower): Fu[Result] =
if DisposableEmailDomain.whitelisted(domain.into(Domain)) then fuccess(Result.Passlist)
Expand Down Expand Up @@ -95,6 +96,14 @@ final class EmailAddressValidator(
case (acc, _) => acc
if variations.isEmpty then List(email) else variations

private def isInfiniteAlias(e: EmailAddress) =
duckAliases.is(e)

private object duckAliases:
private val domain = Domain.Lower.from("duck.com")
private val regex = """^\w{3,}-\w{3,}-\w{3,}$""".r
def is(e: EmailAddress) = e.nameAndDomain.exists((n, d) => d.lower == domain && regex.matches(n))

private def wasUsedTwiceRecently(email: EmailAddress): Fu[Boolean] =
userRepo.countRecentByPrevEmail(email.normalize, nowInstant.minusWeeks(1)).dmap(_ >= 2) >>|
userRepo.countRecentByPrevEmail(email.normalize, nowInstant.minusMonths(1)).dmap(_ >= 4)
Expand All @@ -106,10 +115,8 @@ object EmailAddressValidator:
case Alright extends Result(none)
case DomainMissing extends Result("The email address domain is missing.".some) // no translation needed
case Blocklist extends Result("Cannot use disposable email addresses (Blocklist).".some)
case Alias extends Result("Cannot use email address aliases.".some)
case DnsMissing extends Result("This email domain doesn't seem to work (missing MX DNS)".some)
case DnsTimeout extends Result("This email domain doesn't seem to work (timeout MX DNS)".some)
case DnsBlocklist
extends Result(
"Cannot use disposable email addresses (DNS blocklist).".some
)
case Reputation extends Result("This email domain has a poor reputation and cannot be used.".some)
case DnsBlocklist extends Result("Cannot use disposable email addresses (DNS blocklist).".some)
case Reputation extends Result("This email domain has a poor reputation and cannot be used.".some)
2 changes: 1 addition & 1 deletion modules/security/src/main/VerifyMail.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ final private class VerifyMail(
if res.status == 429
then
logger.info(s"Mailcheck rate limited $url")
rateLimitedUntil = nowInstant.plusMinutes(2)
rateLimitedUntil = nowInstant.plusMinutes(5)
true
else
(for
Expand Down
2 changes: 1 addition & 1 deletion modules/study/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ final class Env(

private lazy val chapterMaker = wire[ChapterMaker]

private lazy val explorerGame = wire[ExplorerGame]
private lazy val explorerGame = wire[ExplorerGameApi]

private lazy val studyMaker = wire[StudyMaker]

Expand Down
2 changes: 1 addition & 1 deletion modules/study/src/main/ExplorerGame.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import chess.format.{ Fen, UciPath }
import lila.tree.Node.Comment
import lila.tree.{ Branch, Node, Root }

final private class ExplorerGame(
final private class ExplorerGameApi(
explorer: lila.core.game.Explorer,
namer: lila.core.game.Namer,
lightUserApi: lila.core.user.LightUserApi,
Expand Down
2 changes: 1 addition & 1 deletion modules/study/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,5 @@ object JsonView:

private[study] given Writes[Chapter.ServerEval] = Json.writes

private[study] given OWrites[actorApi.Who] = OWrites: w =>
private[study] given OWrites[Who] = OWrites: w =>
Json.obj("u" -> w.u, "s" -> w.sri)
36 changes: 14 additions & 22 deletions modules/study/src/main/StudyApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ import lila.core.timeline.{ Propagate, StudyLike }
import lila.tree.Branch
import lila.tree.Node.{ Comment, Gamebook, Shapes }

import actorApi.Who

final class StudyApi(
studyRepo: StudyRepo,
chapterRepo: ChapterRepo,
sequencer: StudySequencer,
studyMaker: StudyMaker,
chapterMaker: ChapterMaker,
inviter: StudyInvite,
explorerGameHandler: ExplorerGame,
explorerGameHandler: ExplorerGameApi,
topicApi: StudyTopicApi,
lightUserApi: lila.core.user.LightUserApi,
chatApi: lila.core.chat.ChatApi,
Expand Down Expand Up @@ -225,27 +223,21 @@ final class StudyApi(
yield sendTo(study.id)(_.setPath(position, who))
case _ => funit

def addNode(
studyId: StudyId,
position: Position.Ref,
node: Branch,
opts: MoveOpts,
relay: Option[Chapter.Relay] = None
)(who: Who): Funit =
sequenceStudyWithChapter(studyId, position.chapterId):
def addNode(args: AddNode): Funit =
import args.{ *, given }
sequenceStudyWithChapter(studyId, positionRef.chapterId):
case Study.WithChapter(study, chapter) =>
Contribute(who.u, study):
doAddNode(study, Position(chapter, position.path), node, opts, relay)(who)
doAddNode(args, study, Position(chapter, positionRef.path))
.flatMapz { _() }

private def doAddNode(
args: AddNode,
study: Study,
position: Position,
rawNode: Branch,
opts: MoveOpts,
relay: Option[Chapter.Relay]
)(who: Who): Fu[Option[() => Funit]] =
val singleNode = rawNode.withoutChildren
position: Position
): Fu[Option[() => Funit]] =
import args.{ *, given }
val singleNode = args.node.withoutChildren
def failReload() = reloadSriBecauseOf(study, who.sri, position.chapter.id)
if position.chapter.isOverweight then
logger.info(s"Overweight chapter ${study.id}/${position.chapter.id}")
Expand All @@ -272,7 +264,7 @@ final class StudyApi(
isMainline = newPosition.path.isMainline(chapter.root)
promoteToMainline = opts.promoteToMainline && !isMainline
yield promoteToMainline.option: () =>
promote(study.id, position.ref + node, toMainline = true)(who)
promote(study.id, position.ref + node, toMainline = true)
}
}

Expand Down Expand Up @@ -326,7 +318,7 @@ final class StudyApi(
yield onChapterChange(study.id, chapter.id, who)

// rewrites the whole chapter because of `forceVariation`. Very inefficient.
def promote(studyId: StudyId, position: Position.Ref, toMainline: Boolean)(who: Who): Funit =
def promote(studyId: StudyId, position: Position.Ref, toMainline: Boolean)(using who: Who): Funit =
sequenceStudyWithChapter(studyId, position.chapterId):
case Study.WithChapter(study, chapter) =>
Contribute(who.u, study):
Expand Down Expand Up @@ -441,7 +433,7 @@ final class StudyApi(
reloadSriBecauseOf(sc.study, who.sri, position.chapterId)
fufail(s"Invalid setClock $position $clock")

def setTag(studyId: StudyId, setTag: actorApi.SetTag)(who: Who) =
def setTag(studyId: StudyId, setTag: SetTag)(who: Who) =
sequenceStudyWithChapter(studyId, setTag.chapterId):
case Study.WithChapter(study, chapter) =>
Contribute(who.u, study):
Expand Down Expand Up @@ -545,7 +537,7 @@ final class StudyApi(
reloadSriBecauseOf(study, who.sri, chapter.id)
fufail(s"Invalid setGamebook $studyId $position")

def explorerGame(studyId: StudyId, data: actorApi.ExplorerGame)(who: Who) =
def explorerGame(studyId: StudyId, data: ExplorerGame)(who: Who) =
sequenceStudyWithChapter(studyId, data.position.chapterId):
case Study.WithChapter(study, chapter) =>
Contribute(who.u, study):
Expand Down
Loading

0 comments on commit 4ddfb03

Please sign in to comment.