export game PGN with real player names and ratings through external file
curl 'l.org/game/export/FUPEWMpY?players=0ff573018f/gistfile1.txt
'
pull/6962/head
parent
6e184110df
commit
df655e9e23
|
@ -42,7 +42,8 @@ final class Game(
|
|||
format = if (HTTPRequest acceptsJson req) GameApiV2.Format.JSON else GameApiV2.Format.PGN,
|
||||
imported = getBool("imported", req),
|
||||
flags = requestPgnFlags(req, extended = true),
|
||||
noDelay = get("key", req).exists(env.noDelaySecretSetting.get().value.contains)
|
||||
noDelay = get("key", req).exists(env.noDelaySecretSetting.get().value.contains),
|
||||
playerFile = get("players", req)
|
||||
)
|
||||
env.api.gameApiV2.exportOne(game, config) flatMap { content =>
|
||||
env.api.gameApiV2.filename(game, config.format) map { filename =>
|
||||
|
|
|
@ -40,6 +40,7 @@ final class Env(
|
|||
onlineApiUsers: lila.bot.OnlineApiUsers,
|
||||
challengeEnv: lila.challenge.Env,
|
||||
msgEnv: lila.msg.Env,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
ws: WSClient,
|
||||
val mode: Mode
|
||||
)(implicit
|
||||
|
@ -56,6 +57,8 @@ final class Env(
|
|||
|
||||
lazy val gameApi = wire[GameApi]
|
||||
|
||||
lazy val realPlayers = wire[RealPlayerApi]
|
||||
|
||||
lazy val gameApiV2 = wire[GameApiV2]
|
||||
|
||||
lazy val userGameApi = wire[UserGameApi]
|
||||
|
|
|
@ -27,7 +27,8 @@ final class GameApiV2(
|
|||
playerRepo: lila.tournament.PlayerRepo,
|
||||
swissApi: lila.swiss.SwissApi,
|
||||
analysisRepo: lila.analyse.AnalysisRepo,
|
||||
getLightUser: LightUser.Getter
|
||||
getLightUser: LightUser.Getter,
|
||||
realPlayerApi: RealPlayerApi
|
||||
)(implicit
|
||||
ec: scala.concurrent.ExecutionContext,
|
||||
system: akka.actor.ActorSystem
|
||||
|
@ -47,13 +48,21 @@ final class GameApiV2(
|
|||
game.pgnImport ifTrue config.imported match {
|
||||
case Some(imported) => fuccess(imported.pgn)
|
||||
case None =>
|
||||
enrich(config.flags)(game) flatMap {
|
||||
case (game, initialFen, analysis) =>
|
||||
config.format match {
|
||||
case Format.JSON => toJson(game, initialFen, analysis, config.flags) dmap Json.stringify
|
||||
case Format.PGN => pgnDump.toPgnString(game, initialFen, analysis, config.flags)
|
||||
}
|
||||
}
|
||||
for {
|
||||
realPlayers <- config.playerFile.??(realPlayerApi.apply)
|
||||
(game, initialFen, analysis) <- enrich(config.flags)(game)
|
||||
export <- config.format match {
|
||||
case Format.JSON => toJson(game, initialFen, analysis, config.flags) dmap Json.stringify
|
||||
case Format.PGN =>
|
||||
pgnDump(
|
||||
game,
|
||||
initialFen,
|
||||
analysis,
|
||||
config.flags,
|
||||
realPlayers = realPlayers
|
||||
) dmap pgnDump.toPgnString
|
||||
}
|
||||
} yield export
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +249,8 @@ final class GameApiV2(
|
|||
lightUsers <- gameLightUsers(g)
|
||||
pgn <-
|
||||
withFlags.pgnInJson ?? pgnDump
|
||||
.toPgnString(g, initialFen, analysisOption, withFlags)
|
||||
.apply(g, initialFen, analysisOption, withFlags)
|
||||
.dmap(pgnDump.toPgnString)
|
||||
.dmap(some)
|
||||
} yield Json
|
||||
.obj(
|
||||
|
@ -307,7 +317,8 @@ object GameApiV2 {
|
|||
format: Format,
|
||||
imported: Boolean,
|
||||
flags: WithFlags,
|
||||
noDelay: Boolean
|
||||
noDelay: Boolean,
|
||||
playerFile: Option[String]
|
||||
) extends Config
|
||||
|
||||
case class ByUserConfig(
|
||||
|
|
|
@ -22,7 +22,8 @@ final class PgnDump(
|
|||
initialFen: Option[FEN],
|
||||
analysis: Option[Analysis],
|
||||
flags: WithFlags,
|
||||
teams: Option[GameTeams] = None
|
||||
teams: Option[GameTeams] = None,
|
||||
realPlayers: Option[RealPlayers] = None
|
||||
): Fu[Pgn] =
|
||||
dumper(game, initialFen, flags, teams) flatMap { pgn =>
|
||||
if (flags.tags) (game.simulId ?? simulApi.idToName) map { simulName =>
|
||||
|
@ -36,6 +37,8 @@ final class PgnDump(
|
|||
val evaled = analysis.ifTrue(flags.evals).fold(pgn)(addEvals(pgn, _))
|
||||
if (flags.literate) annotator(evaled, analysis, game.opening, game.winnerColor, game.status)
|
||||
else evaled
|
||||
} map { pgn =>
|
||||
realPlayers.fold(pgn)(_.update(game, pgn))
|
||||
}
|
||||
|
||||
private def addEvals(p: Pgn, analysis: Analysis): Pgn =
|
||||
|
@ -60,19 +63,12 @@ final class PgnDump(
|
|||
|
||||
def formatter(flags: WithFlags) =
|
||||
(game: Game, initialFen: Option[FEN], analysis: Option[Analysis], teams: Option[GameTeams]) =>
|
||||
toPgnString(game, initialFen, analysis, flags, teams)
|
||||
apply(game, initialFen, analysis, flags, teams) dmap toPgnString
|
||||
|
||||
def toPgnString(
|
||||
game: Game,
|
||||
initialFen: Option[FEN],
|
||||
analysis: Option[Analysis],
|
||||
flags: WithFlags,
|
||||
teams: Option[GameTeams] = None
|
||||
) =
|
||||
apply(game, initialFen, analysis, flags, teams) dmap { pgn =>
|
||||
// merge analysis & eval comments
|
||||
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
|
||||
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }
|
||||
s"$pgn\n\n\n".replaceIf("] } { [", "] [")
|
||||
}
|
||||
def toPgnString(pgn: Pgn) = {
|
||||
// merge analysis & eval comments
|
||||
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
|
||||
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }
|
||||
s"$pgn\n\n\n".replaceIf("] } { [", "] [")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package lila.api
|
||||
|
||||
import chess.format.pgn.{ Pgn, Tag, Tags }
|
||||
import play.api.libs.ws.WSClient
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.user.User
|
||||
|
||||
final class RealPlayerApi(
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
ws: WSClient
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
def apply(url: String): Fu[Option[RealPlayers]] = cache get url
|
||||
|
||||
private val cache = cacheApi[String, Option[RealPlayers]](4, "api.realPlayer") {
|
||||
_.expireAfterAccess(10 seconds)
|
||||
.buildAsyncFuture { url =>
|
||||
ws.url(url)
|
||||
.withRequestTimeout(3.seconds)
|
||||
.get()
|
||||
.map { res =>
|
||||
val valid =
|
||||
res.status == 200 &&
|
||||
res.headers
|
||||
.get("Content-Type")
|
||||
.exists(_.exists(_ startsWith "text/plain"))
|
||||
valid ?? {
|
||||
res.body.linesIterator
|
||||
.take(9999)
|
||||
.toList
|
||||
.flatMap { line =>
|
||||
line.split(';').map(_.trim) match {
|
||||
case Array(id, name, rating) =>
|
||||
Some(id -> RealPlayer(name.some.filter(_.nonEmpty), rating.toIntOption))
|
||||
case Array(id, name) => Some(id -> RealPlayer(name.some.filter(_.nonEmpty), none))
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
.toMap
|
||||
.some
|
||||
.map(RealPlayers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class RealPlayers(players: Map[User.ID, RealPlayer]) {
|
||||
|
||||
def update(game: lila.game.Game, pgn: Pgn) =
|
||||
pgn.copy(
|
||||
tags = pgn.tags ++ Tags {
|
||||
game.players.flatMap { player =>
|
||||
player.userId.flatMap(players.get) ?? { rp =>
|
||||
List(
|
||||
rp.name.map { name => Tag(player.color.fold(Tag.White, Tag.Black), name) },
|
||||
rp.rating.map { rating => Tag(player.color.fold(Tag.WhiteElo, Tag.BlackElo), rating.toString) }
|
||||
).flatten
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
case class RealPlayer(name: Option[String], rating: Option[Int])
|
Loading…
Reference in New Issue