Merge branch 'master' into analyse
* master: don't show cheater trophies better exports caching remove export logs better png export logging Revert "Bam! And the footer is fixed." pt "Português" translation #10165. Author: Arnica. Changed "spectatators" to a way that fulfill both Brazilian Portuguese and Portuguese. better external process logging fix round JS upgrade evaluator submodule include engine evaluator as a submodule Move Print button into FEN & PGN Menu log exports mt "Malti" translation #10164. Author: kittenthomas. fix auto report duplication stop auto block reports use board image creation in the og headers - through CDN expose PDF service print game to PDF, stream it as chunked HTTP and cache it in CDN Conflicts: app/views/analyse/replay.scala.html
This commit is contained in:
commit
ba5bf79dc2
9
.gitmodules
vendored
9
.gitmodules
vendored
|
@ -10,3 +10,12 @@
|
|||
[submodule "public/staunton"]
|
||||
path = public/staunton
|
||||
url = https://github.com/clarkerubber/Staunton-Pieces
|
||||
[submodule "submodules/pdfexporter"]
|
||||
path = submodules/pdfexporter
|
||||
url = https://github.com/clarkerubber/lichessPDFExporter
|
||||
[submodule "submodules/boardcreator"]
|
||||
path = submodules/boardcreator
|
||||
url = https://github.com/clarkerubber/board-creator
|
||||
[submodule "submodules/evaluator"]
|
||||
path = submodules/evaluator
|
||||
url = https://github.com/clarkerubber/engine-evaluator
|
||||
|
|
|
@ -31,7 +31,7 @@ private[app] final class Router(
|
|||
case User(username) => sender ! R.User.show(username).url
|
||||
case Player(fullId) => sender ! R.Round.player(fullId).url
|
||||
case Watcher(gameId, color) => sender ! R.Round.watcher(gameId, color).url
|
||||
case Pgn(gameId) => sender ! R.Analyse.pgn(gameId).url
|
||||
case Pgn(gameId) => sender ! R.Export.pgn(gameId).url
|
||||
case Tourney(tourId) => sender ! R.Tournament.show(tourId).url
|
||||
case Puzzle(id) => sender ! R.Puzzle.show(id).url
|
||||
|
||||
|
|
|
@ -72,22 +72,6 @@ object Analyse extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def pgn(id: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo game id) { game =>
|
||||
(game.pgnImport.ifTrue(~get("as") == "imported") match {
|
||||
case Some(i) => fuccess(i.pgn)
|
||||
case None => for {
|
||||
pgn ← Env.game.pgnDump(game)
|
||||
analysis ← (~get("as") != "raw") ?? (env.analyser getDone game.id)
|
||||
} yield Env.analyse.annotator(pgn, analysis, gameOpening(game), game.winnerColor, game.status, game.clock).toString
|
||||
}) map { content =>
|
||||
Ok(content).withHeaders(
|
||||
CONTENT_TYPE -> ContentTypes.TEXT,
|
||||
CONTENT_DISPOSITION -> ("attachment; filename=" + (Env.game.pgnDump filename game)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def gameOpening(game: GameModel) =
|
||||
if (game.fromPosition || game.variant.exotic) none
|
||||
else chess.OpeningExplorer openingOf game.pgnMoves
|
||||
|
|
50
app/controllers/Export.scala
Normal file
50
app/controllers/Export.scala
Normal file
|
@ -0,0 +1,50 @@
|
|||
package controllers
|
||||
|
||||
import play.api.mvc.Action
|
||||
|
||||
import lila.app._
|
||||
import lila.game.{ Game => GameModel, GameRepo }
|
||||
import play.api.http.ContentTypes
|
||||
import play.api.libs.iteratee.{ Iteratee, Enumerator }
|
||||
import views._
|
||||
|
||||
object Export extends LilaController {
|
||||
|
||||
private def env = Env.game
|
||||
|
||||
def pgn(id: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo game id) { game =>
|
||||
(game.pgnImport.ifTrue(~get("as") == "imported") match {
|
||||
case Some(i) => fuccess(i.pgn)
|
||||
case None => for {
|
||||
pgn ← Env.game.pgnDump(game)
|
||||
analysis ← (~get("as") != "raw") ?? (Env.analyse.analyser getDone game.id)
|
||||
} yield Env.analyse.annotator(pgn, analysis, gameOpening(game), game.winnerColor, game.status, game.clock).toString
|
||||
}) map { content =>
|
||||
Ok(content).withHeaders(
|
||||
CONTENT_TYPE -> ContentTypes.TEXT,
|
||||
CONTENT_DISPOSITION -> ("attachment; filename=" + (Env.game.pgnDump filename game)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def pdf(id: String) = Open { implicit ctx =>
|
||||
OptionResult(GameRepo game id) { game =>
|
||||
Ok.chunked(Enumerator.outputStream(env.pdfExport(game.id))).withHeaders(
|
||||
CONTENT_TYPE -> "application/pdf",
|
||||
CACHE_CONTROL -> "max-age=7200")
|
||||
}
|
||||
}
|
||||
|
||||
def png(id: String) = Open { implicit ctx =>
|
||||
OptionResult(GameRepo game id) { game =>
|
||||
Ok.chunked(Enumerator.outputStream(env.pngExport(game))).withHeaders(
|
||||
CONTENT_TYPE -> "image/png",
|
||||
CACHE_CONTROL -> "max-age=7200")
|
||||
}
|
||||
}
|
||||
|
||||
private def gameOpening(game: GameModel) =
|
||||
if (game.fromPosition || game.variant.exotic) none
|
||||
else chess.OpeningExplorer openingOf game.pgnMoves
|
||||
}
|
|
@ -14,6 +14,7 @@ trait AssetHelper {
|
|||
|
||||
val assetBaseUrl = s"http://$assetDomain"
|
||||
|
||||
def cdnUrl(path: String) = s"$assetBaseUrl$path"
|
||||
def staticUrl(path: String) = s"$assetBaseUrl${routes.Assets.at(path)}"
|
||||
|
||||
def cssTag(name: String, staticDomain: Boolean = true) = cssAt("stylesheets/" + name, staticDomain)
|
||||
|
|
|
@ -14,6 +14,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
|
||||
def netBaseUrl: String
|
||||
def staticUrl(path: String): String
|
||||
def cdnUrl(path: String): String
|
||||
|
||||
def mandatorySecondsToMove = lila.game.Env.current.MandatorySecondsToMove
|
||||
|
||||
|
@ -22,7 +23,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
val variant = pov.game.variant.exotic ?? s" ${pov.game.variant.name}"
|
||||
Map(
|
||||
'type -> "website",
|
||||
'image -> staticUrl("images/large_tile.png"),
|
||||
'image -> cdnUrl(routes.Export.png(pov.game.id).url),
|
||||
'title -> s"$speed$variant Chess - ${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)}",
|
||||
'site_name -> "lichess.org",
|
||||
'url -> s"$netBaseUrl${routes.Round.watcher(pov.game.id, pov.color.name).url}",
|
||||
|
|
|
@ -114,61 +114,28 @@ openGraph = povOpenGraph(pov)) {
|
|||
}
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
<div class="underboard_content" style="display:none">
|
||||
<div class="analysis_panels">
|
||||
@if(game.analysable) {
|
||||
<div class="panel computer_analysis">
|
||||
@analysis.map { a =>
|
||||
@advantageChart.map { chart =>
|
||||
<div
|
||||
id="adv_chart"
|
||||
data-title="Advantage (up: white, down: black)"
|
||||
data-max="@lila.analyse.AdvantageChart.max"
|
||||
data-rows="@chart"></div>
|
||||
}.getOrElse {
|
||||
@analyse.computing()
|
||||
<div class="panel fen_pgn">
|
||||
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
|
||||
<p><strong>PGN</strong>
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)"> Download annotated</a>
|
||||
@if(analysis.isDefined) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=raw"> Download raw</a>
|
||||
}
|
||||
}.getOrElse {
|
||||
@if(analysis.isEmpty) {
|
||||
<form class="future_game_analysis@if(ctx.isAnon) { must_login }" action="@routes.Analyse.requestAnalysis(gameId)" method="post">
|
||||
<button type="submit" class="button"><span class="is3" data-icon="A"> @trans.requestAComputerAnalysis()</span></button>
|
||||
</form>
|
||||
@if(game.isPgnImport) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=imported"> Download imported</a>
|
||||
}
|
||||
}
|
||||
<div class="view_game_analysis future_game_analysis" data-href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<a class="button" href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<span class="is3" data-icon="A"> @trans.viewTheComputerAnalysis()</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="panel fen_pgn">
|
||||
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
|
||||
<p><strong>PGN</strong>
|
||||
<a data-icon="x" href="@routes.Analyse.pgn(game.id)"> Download annotated</a>
|
||||
@if(analysis.isDefined) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Analyse.pgn(game.id)?as=raw"> Download raw</a>
|
||||
}
|
||||
@if(game.isPgnImport) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Analyse.pgn(game.id)?as=imported"> Download imported</a>
|
||||
}
|
||||
</p>
|
||||
<div class="pgn">@Html(nl2br(escape(pgn)))</div>
|
||||
</div>
|
||||
<div class="panel move_times">
|
||||
<div
|
||||
id="movetimes_chart"
|
||||
data-series="@timeChart.series"
|
||||
data-max="@timeChart.maxTime"></div>
|
||||
</div>
|
||||
@cross.map { c =>
|
||||
<div class="panel crosstable">
|
||||
@views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov))
|
||||
</div>
|
||||
}
|
||||
/
|
||||
<a data-icon="x" target="_blank" href="@cdnUrl(routes.Export.pdf(game.id).url)"> Print-friendly PDF</a>
|
||||
</p>
|
||||
<div class="pgn">@Html(nl2br(escape(pgn)))</div>
|
||||
</div>
|
||||
<div class="panel move_times">
|
||||
<div
|
||||
id="movetimes_chart"
|
||||
data-series="@timeChart.series"
|
||||
data-max="@timeChart.maxTime"></div>
|
||||
</div>
|
||||
<div class="analysis_menu">
|
||||
@if(game.analysable) {
|
||||
|
|
|
@ -63,6 +63,7 @@ openGraph = Map(
|
|||
'description -> describeUser(u))) {
|
||||
<div class="content_box no_padding user_show">
|
||||
<div class="content_box_top">
|
||||
@if(!u.engine) {
|
||||
@info.ranks.map {
|
||||
case (perf, rank) if rank == 1 => {
|
||||
<span class="trophy hint--left" data-hint="@PerfType.name(perf) Champion!">
|
||||
|
@ -86,6 +87,7 @@ openGraph = Map(
|
|||
}
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
<h1 class="lichess_title"><span@if(isOnline(u.id)) { class="connected" } data-icon="r"></span> @u.titleUsername</h1>
|
||||
@if(u.disabled) {
|
||||
<span class="staff">CLOSED</span>
|
||||
|
|
|
@ -74,7 +74,8 @@ fi
|
|||
|
||||
lilalog "Rsync scripts, binaries and assets"
|
||||
stage="target/universal/stage"
|
||||
rsync_command="rsync $RSYNC_OPTIONS bin $stage/bin $stage/lib public $REMOTE:$REMOTE_DIR"
|
||||
include="bin $stage/bin $stage/lib public submodules"
|
||||
rsync_command="rsync $RSYNC_OPTIONS $include $REMOTE:$REMOTE_DIR"
|
||||
echo "$rsync_command"
|
||||
$rsync_command
|
||||
echo "rsync complete"
|
||||
|
|
34
conf/messages.mt
Normal file
34
conf/messages.mt
Normal file
|
@ -0,0 +1,34 @@
|
|||
playWithAFriend=Ilgħab ma' ħabib
|
||||
playWithTheMachine=Ilgħab mal-kompjuter
|
||||
toInviteSomeoneToPlayGiveThisUrl=Biex tistienden lil xi ħadd biex jilgħab, għatih/ha dan il-link
|
||||
gameOver=Spiċċat il-logħba
|
||||
waitingForOpponent=Stenna l-għadu
|
||||
waiting=Stenna
|
||||
yourTurn=Inti jmiss
|
||||
aiNameLevelAiLevel=%s livell %s
|
||||
level=Livell
|
||||
toggleTheChat=Uża iċ-chat
|
||||
toggleSound=Irranġa l-ħoss
|
||||
chat=Chat
|
||||
resign=Rrisenja
|
||||
checkmate=Checkmate
|
||||
stalemate=Stalemate
|
||||
white=Abjad
|
||||
black=Iswed
|
||||
randomColor=Kulur li jkun
|
||||
createAGame=Oħloq logħba
|
||||
whiteIsVictorious=L-Abjad rebaħ il-logħba
|
||||
blackIsVictorious=L-Iswed rebaħ il-logħba
|
||||
playWithTheSameOpponentAgain=Ilgħab kontra l-istess għadu
|
||||
newOpponent=Għadu ġdid
|
||||
playWithAnotherOpponent=Ilgħab ma' għadu ieħor
|
||||
yourOpponentWantsToPlayANewGameWithYou=L-għadu jixtieq jilgħab logħba oħra miegħek
|
||||
joinTheGame=Ilqa' din l-istedina biex tilgħab din il-logħba
|
||||
whitePlays=Abjad jmissu jilgħab
|
||||
blackPlays=Iswed jmissu jilgħab
|
||||
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=L-għadu seta' telaq il-logħba. Tista' tieħu r-rebħa, tgħid illi ġiet draw jew tistenna.
|
||||
makeYourOpponentResign=Sforza ir-rebħa
|
||||
forceResignation=Ħu r-rebħa
|
||||
forceDraw=Għid illi ġiet draw
|
||||
talkInChat=Tkellem b'chat
|
||||
theFirstPersonToComeOnThisUrlWillPlayWithYou=L-ewwel persuna illi jiġi permezz tal-URL, ser jilgħab miegħek
|
|
@ -128,7 +128,7 @@ recipient=Recipiente
|
|||
send=Enviar
|
||||
incrementInSeconds=Acréscimo em segundos
|
||||
freeOnlineChess=Xadrez Online Gratuito
|
||||
spectators=Espetadores:
|
||||
spectators=Espectadores:
|
||||
nbWins=%s vitórias
|
||||
nbLosses=%s derrotas
|
||||
nbDraws=%s empates
|
||||
|
|
|
@ -136,7 +136,10 @@ POST /team/:id/kick controllers.Team.kick(id: String)
|
|||
POST /$gameId<\w{8}>/request-analysis controllers.Analyse.requestAnalysis(gameId: String)
|
||||
POST /$gameId<\w{8}>/better-analysis/$color<white|black> controllers.Analyse.betterAnalysis(gameId: String, color: String)
|
||||
POST /$gameId<\w{8}>/post-analysis controllers.Analyse.postAnalysis(gameId: String)
|
||||
GET /$gameId<\w{8}>/pgn controllers.Analyse.pgn(gameId: String)
|
||||
|
||||
GET /game/pgn/$gameId<\w{8}>.pgn controllers.Export.pgn(gameId: String)
|
||||
GET /game/export/pdf/$gameId<\w{8}>.pdf controllers.Export.pdf(gameId: String)
|
||||
GET /game/export/png/$gameId<\w{8}>.png controllers.Export.png(gameId: String)
|
||||
|
||||
# Pref
|
||||
POST /pref/:name controllers.Pref.set(name: String)
|
||||
|
|
|
@ -10,14 +10,14 @@ final class Env(
|
|||
system: ActorSystem) {
|
||||
|
||||
private val CollectionEvaluation = config getString "collection.evaluation"
|
||||
private val EvaluatorScriptPath = config getString "evaluator.script_path"
|
||||
private val EvaluatorExecPath = config getString "evaluator.exec_path"
|
||||
private val ActorName = config getString "actor.name"
|
||||
private val ApiToken = config getString "api.token"
|
||||
private val ApiUrl = config getString "api.url"
|
||||
|
||||
lazy val evaluator = new Evaluator(
|
||||
coll = db(CollectionEvaluation),
|
||||
script = EvaluatorScriptPath,
|
||||
execPath = EvaluatorExecPath,
|
||||
reporter = hub.actor.report,
|
||||
analyser = hub.actor.analyser,
|
||||
marker = hub.actor.mod,
|
||||
|
|
|
@ -19,7 +19,7 @@ import lila.user.{ User, UserRepo, Perfs }
|
|||
|
||||
final class Evaluator(
|
||||
coll: Coll,
|
||||
script: String,
|
||||
execPath: String,
|
||||
reporter: ActorSelection,
|
||||
analyser: ActorSelection,
|
||||
marker: ActorSelection,
|
||||
|
@ -116,12 +116,13 @@ final class Evaluator(
|
|||
}
|
||||
|
||||
private def run(userId: String, deep: Boolean): Try[String] = {
|
||||
val command = s"""$script $userId ${deep.fold("true", "false")} $token $apiUrl/"""
|
||||
import scala.sys.process._
|
||||
import java.io.File
|
||||
val exec = Process(Seq("php", "engine-evaluator.php", userId, deep.fold("true", "false"), token, s"$apiUrl/"), new File(execPath))
|
||||
Try {
|
||||
import scala.sys.process._
|
||||
command!!
|
||||
exec.!!
|
||||
} match {
|
||||
case Failure(e) => Failure(new Exception(s"$command $e"))
|
||||
case Failure(e) => Failure(new Exception(s"$exec $e"))
|
||||
case x => x
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ final class Env(
|
|||
val ActorName = config getString "actor.name"
|
||||
val UciMemoTtl = config duration "uci_memo.ttl"
|
||||
val netBaseUrl = config getString "net.base_url"
|
||||
val PdfExecPath = config getString "pdf.exec_path"
|
||||
val PngExecPath = config getString "png.exec_path"
|
||||
}
|
||||
import settings._
|
||||
|
||||
|
@ -35,6 +37,10 @@ final class Env(
|
|||
|
||||
private[game] lazy val gameColl = db(CollectionGame)
|
||||
|
||||
lazy val pdfExport = PdfExport(PdfExecPath) _
|
||||
|
||||
lazy val pngExport = PngExport(PngExecPath) _
|
||||
|
||||
lazy val cached = new Cached(ttl = CachedNbTtl)
|
||||
|
||||
lazy val paginator = new PaginatorBuilder(
|
||||
|
|
|
@ -63,7 +63,7 @@ case class Game(
|
|||
|
||||
def opponent(c: Color): Player = player(!c)
|
||||
|
||||
private lazy val firstColor = (whitePlayer before blackPlayer).fold(White, Black)
|
||||
lazy val firstColor = (whitePlayer before blackPlayer).fold(White, Black)
|
||||
def firstPlayer = player(firstColor)
|
||||
def secondPlayer = player(!firstColor)
|
||||
|
||||
|
|
14
modules/game/src/main/PdfExport.scala
Normal file
14
modules/game/src/main/PdfExport.scala
Normal file
|
@ -0,0 +1,14 @@
|
|||
package lila.game
|
||||
|
||||
import java.io.{ File, OutputStream }
|
||||
import scala.sys.process._
|
||||
|
||||
object PdfExport {
|
||||
|
||||
private val logger = ProcessLogger(_ => (), _ => ())
|
||||
|
||||
def apply(execPath: String)(id: String)(out: OutputStream) {
|
||||
val exec = Process(Seq("php", "main.php", id), new File(execPath))
|
||||
exec #> out ! logger
|
||||
}
|
||||
}
|
18
modules/game/src/main/PngExport.scala
Normal file
18
modules/game/src/main/PngExport.scala
Normal file
|
@ -0,0 +1,18 @@
|
|||
package lila.game
|
||||
|
||||
import chess.format.Forsyth
|
||||
import java.io.{ File, OutputStream }
|
||||
import scala.sys.process._
|
||||
|
||||
object PngExport {
|
||||
|
||||
private val logger = ProcessLogger(_ => (), _ => ())
|
||||
|
||||
def apply(execPath: String)(game: Game)(out: OutputStream) {
|
||||
val fen = (Forsyth >> game.toChess).split(' ').head
|
||||
val color = game.firstColor.letter.toString
|
||||
val lastMove = ~game.castleLastMoveTime.lastMoveString
|
||||
val exec = Process(Seq("php", "board-creator.php", fen, color, lastMove), new File(execPath))
|
||||
exec #> out ! logger
|
||||
}
|
||||
}
|
|
@ -43,7 +43,6 @@ case object GetUids
|
|||
package report {
|
||||
case class Cheater(userId: String, text: String)
|
||||
case class Check(userId: String)
|
||||
case class Blocked(userId: String, blocked: Int, followed: Int)
|
||||
}
|
||||
|
||||
package mod {
|
||||
|
|
|
@ -22,6 +22,7 @@ final class Env(
|
|||
val CollectionTranslation = config getString "collection.translation"
|
||||
val ContextGitUrl = config getString "context.git.url"
|
||||
val ContextGitFile = config getString "context.git.file"
|
||||
val CdnDomain = config getString "cdn_domain"
|
||||
}
|
||||
import settings._
|
||||
|
||||
|
@ -40,7 +41,10 @@ final class Env(
|
|||
|
||||
lazy val keys = new I18nKeys(translator)
|
||||
|
||||
lazy val requestHandler = new I18nRequestHandler(pool, RequestHandlerProtocol)
|
||||
lazy val requestHandler = new I18nRequestHandler(
|
||||
pool,
|
||||
RequestHandlerProtocol,
|
||||
CdnDomain)
|
||||
|
||||
lazy val jsDump = new JsDump(
|
||||
path = appPath + "/" + WebPathRelative,
|
||||
|
|
|
@ -6,12 +6,18 @@ import play.api.mvc.{ Action, RequestHeader, Handler }
|
|||
|
||||
import lila.common.HTTPRequest
|
||||
|
||||
final class I18nRequestHandler(pool: I18nPool, protocol: String) {
|
||||
final class I18nRequestHandler(
|
||||
pool: I18nPool,
|
||||
protocol: String,
|
||||
cdnDomain: String) {
|
||||
|
||||
def apply(req: RequestHeader): Option[Handler] =
|
||||
(HTTPRequest.isRedirectable(req) && !pool.domainLang(req).isDefined) option Action {
|
||||
Redirect(redirectUrl(req))
|
||||
}
|
||||
(HTTPRequest.isRedirectable(req) &&
|
||||
!pool.domainLang(req).isDefined &&
|
||||
req.host != cdnDomain
|
||||
) option Action {
|
||||
Redirect(redirectUrl(req))
|
||||
}
|
||||
|
||||
private def redirectUrl(req: RequestHeader) =
|
||||
protocol +
|
||||
|
|
|
@ -7,7 +7,6 @@ import lila.db.api._
|
|||
import lila.db.Implicits._
|
||||
import lila.game.GameRepo
|
||||
import lila.hub.actorApi.relation.ReloadOnlineFriends
|
||||
import lila.hub.actorApi.report.Blocked
|
||||
import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser }
|
||||
import lila.user.tube.userTube
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
|
@ -79,10 +78,7 @@ final class RelationApi(
|
|||
case Some(Block) => funit
|
||||
case _ => RelationRepo.block(u1, u2) >> limitBlock(u1) >> refresh(u1, u2) >>-
|
||||
bus.publish(lila.hub.actorApi.relation.Block(u1, u2), 'relation) >>-
|
||||
(nbBlockers(u2) zip nbFollowers(u2)).andThen {
|
||||
case Success((blockers, followers)) if blockers >= 20 && blockers > followers =>
|
||||
reporter ! Blocked(u2, blockers, followers)
|
||||
}
|
||||
(nbBlockers(u2) zip nbFollowers(u2))
|
||||
}
|
||||
|
||||
def unfollow(u1: ID, u2: ID): Funit =
|
||||
|
|
|
@ -23,8 +23,6 @@ final class Env(
|
|||
def receive = {
|
||||
case lila.hub.actorApi.report.Cheater(userId, text) =>
|
||||
api.autoCheatReport(userId, text)
|
||||
case lila.hub.actorApi.report.Blocked(userId, blocked, followed) =>
|
||||
api.autoBlockReport(userId, blocked, followed)
|
||||
case lila.hub.actorApi.report.Check(userId) =>
|
||||
api.autoProcess(userId)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import tube.reportTube
|
|||
|
||||
private[report] final class ReportApi(evaluator: ActorSelection) {
|
||||
|
||||
def create(setup: ReportSetup, by: User, update: Boolean = false): Funit =
|
||||
def create(setup: ReportSetup, by: User): Funit =
|
||||
Reason(setup.reason).fold[Funit](fufail("Invalid report reason " + setup.reason)) { reason =>
|
||||
val user = setup.user
|
||||
val report = Report.make(
|
||||
|
@ -20,21 +20,17 @@ private[report] final class ReportApi(evaluator: ActorSelection) {
|
|||
reason = reason,
|
||||
text = setup.text,
|
||||
createdBy = by)
|
||||
!isAlreadySlayed(report, user) ?? {
|
||||
findRecent(user, reason) flatMap {
|
||||
case Some(existing) if update =>
|
||||
$update($select(existing.id), $set("text" -> report.text))
|
||||
case Some(_) =>
|
||||
logger.info(s"skip existing report creation: $reason $user")
|
||||
funit
|
||||
case None => $insert(report) >>- {
|
||||
if (report.isCheat && report.isManual) evaluator ! user
|
||||
!isAlreadySlain(report, user) ?? {
|
||||
reportTube.coll.update(
|
||||
selectRecent(user, reason),
|
||||
reportTube.toMongo(report).get,
|
||||
upsert = true) map { res =>
|
||||
if (report.isCheat && !res.updatedExisting) evaluator ! user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def isAlreadySlayed(report: Report, user: User) =
|
||||
private def isAlreadySlain(report: Report, user: User) =
|
||||
(report.isCheat && user.engine) ||
|
||||
(report.isAutomatic && report.isOther && user.troll) ||
|
||||
(report.isTroll && user.troll)
|
||||
|
@ -47,19 +43,6 @@ private[report] final class ReportApi(evaluator: ActorSelection) {
|
|||
reason = "cheat",
|
||||
text = text,
|
||||
gameId = "",
|
||||
move = ""), lichess, update = true)
|
||||
case _ => funit
|
||||
}
|
||||
}
|
||||
|
||||
def autoBlockReport(userId: String, blocked: Int, followed: Int): Funit = {
|
||||
logger.info(s"auto block report $userId: $blocked blockers & $followed followers")
|
||||
UserRepo byId userId zip UserRepo.lichess flatMap {
|
||||
case (Some(user), Some(lichess)) => create(ReportSetup(
|
||||
user = user,
|
||||
reason = "other",
|
||||
text = s"[AUTOREPORT] Blocked $blocked times, followed by $followed players",
|
||||
gameId = "",
|
||||
move = ""), lichess)
|
||||
case _ => funit
|
||||
}
|
||||
|
@ -84,11 +67,13 @@ private[report] final class ReportApi(evaluator: ActorSelection) {
|
|||
|
||||
def recentProcessed(nb: Int) = $find($query(processedSelect) sort $sort.createdDesc, nb)
|
||||
|
||||
private def selectRecent(user: User, reason: Reason) = Json.obj(
|
||||
"createdAt" -> $gt($date(DateTime.now minusDays 3)),
|
||||
"user" -> user.id,
|
||||
"reason" -> reason.name)
|
||||
|
||||
private def findRecent(user: User, reason: Reason): Fu[Option[Report]] =
|
||||
$find.one(Json.obj(
|
||||
"createdAt" -> $gt($date(DateTime.now minusDays 3)),
|
||||
"user" -> user.id,
|
||||
"reason" -> reason.name))
|
||||
$find.one(selectRecent(user, reason))
|
||||
|
||||
private val logger = play.api.Logger("report")
|
||||
}
|
||||
|
|
|
@ -1969,6 +1969,7 @@ var storage = {
|
|||
var $panels = $('div.analysis_panels > div');
|
||||
$('div.analysis_menu').on('click', 'a', function() {
|
||||
var panel = $(this).data('panel');
|
||||
if (!panel) return;
|
||||
$(this).siblings('.active').removeClass('active').end().addClass('active');
|
||||
$panels.removeClass('active').filter('.' + panel).addClass('active');
|
||||
if (panel == 'move_times') try {
|
||||
|
|
|
@ -493,7 +493,6 @@ div.content_box .loader:after {
|
|||
display: none;
|
||||
}
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
|
@ -507,7 +506,6 @@ body {
|
|||
background-image: linear-gradient(to bottom, #d7d7d7 0%, #eeeeee 116px);
|
||||
background-repeat: no-repeat;
|
||||
overflow-x: hidden;
|
||||
margin: 0 0 150px;
|
||||
}
|
||||
a,
|
||||
a:visited,
|
||||
|
@ -605,14 +603,10 @@ body.tight #site_baseline {
|
|||
display: none;
|
||||
}
|
||||
#footer_wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 50px;
|
||||
border-top: 1px solid #c9c9c9;
|
||||
background: #e4e4e4;
|
||||
padding: 30px 0;
|
||||
margin-top: 60px;
|
||||
}
|
||||
#footer_wrap div.footer {
|
||||
width: 1000px;
|
||||
|
|
1
submodules/boardcreator
Submodule
1
submodules/boardcreator
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit a177efd0b4a023e09d8ce8f122c2b904ac312113
|
1
submodules/evaluator
Submodule
1
submodules/evaluator
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 4dffb1beb4a61d53fbffba3d32b4d288fd20ec97
|
1
submodules/pdfexporter
Submodule
1
submodules/pdfexporter
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8d6c9550432ce462e4e977a770751354da097cf4
|
|
@ -8,6 +8,7 @@ module.exports = function(old, cfg) {
|
|||
}
|
||||
|
||||
if (cfg.game.moves) data.game.moves = data.game.moves.split(' ');
|
||||
else data.game.moves = [];
|
||||
|
||||
return data;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue