198 lines
6.5 KiB
Scala
198 lines
6.5 KiB
Scala
package controllers
|
|
|
|
import play.api.i18n.Lang
|
|
import play.api.mvc._
|
|
import scala.concurrent.duration._
|
|
import scala.util.chaining._
|
|
|
|
import lila.app._
|
|
import lila.game.Pov
|
|
import lila.user.{ User => UserModel }
|
|
|
|
// both bot & board APIs
|
|
final class PlayApi(
|
|
env: Env,
|
|
apiC: => Api
|
|
)(implicit
|
|
mat: akka.stream.Materializer
|
|
) extends LilaController(env) {
|
|
|
|
implicit private def autoReqLang(implicit req: RequestHeader) = reqLang(req)
|
|
|
|
// bot endpoints
|
|
|
|
def botGameStream(id: String) =
|
|
Scoped(_.Bot.Play) { implicit req => me =>
|
|
WithPovAsBot(id, me) { impl.gameStream(me, _) }
|
|
}
|
|
|
|
def botMove(id: String, uci: String, offeringDraw: Option[Boolean]) =
|
|
Scoped(_.Bot.Play) { _ => me =>
|
|
WithPovAsBot(id, me) { impl.move(me, _, uci, offeringDraw) }
|
|
}
|
|
|
|
def botCommand(cmd: String) =
|
|
ScopedBody(_.Bot.Play) { implicit req => me =>
|
|
cmd.split('/') match {
|
|
case Array("account", "upgrade") =>
|
|
env.user.repo.isManaged(me.id) flatMap {
|
|
case true => notFoundJson()
|
|
case _ =>
|
|
env.tournament.api.withdrawAll(me) >>
|
|
env.team.cached.teamIdsList(me.id).flatMap { env.swiss.api.withdrawAll(me, _) } >>
|
|
env.user.repo.setBot(me) >>
|
|
env.pref.api.setBot(me) >>
|
|
env.streamer.api.delete(me) >>-
|
|
env.user.lightUserApi.invalidate(me.id) pipe
|
|
toResult recover { case lila.base.LilaInvalid(msg) =>
|
|
BadRequest(jsonError(msg))
|
|
}
|
|
}
|
|
case _ => impl.command(me, cmd)(WithPovAsBot)
|
|
}
|
|
}
|
|
|
|
// board endpoints
|
|
|
|
def boardGameStream(id: String) =
|
|
Scoped(_.Board.Play) { implicit req => me =>
|
|
WithPovAsBoard(id, me) { impl.gameStream(me, _) }
|
|
}
|
|
|
|
def boardMove(id: String, uci: String, offeringDraw: Option[Boolean]) =
|
|
Scoped(_.Board.Play) { _ => me =>
|
|
WithPovAsBoard(id, me) {
|
|
impl.move(me, _, uci, offeringDraw)
|
|
}
|
|
}
|
|
|
|
def boardCommandPost(cmd: String) =
|
|
ScopedBody(_.Board.Play) { implicit req => me =>
|
|
impl.command(me, cmd)(WithPovAsBoard)
|
|
}
|
|
|
|
// common code for bot & board APIs
|
|
private object impl {
|
|
|
|
def gameStream(me: UserModel, pov: Pov)(implicit lang: Lang) =
|
|
env.game.gameRepo.withInitialFen(pov.game) map { wf =>
|
|
apiC.sourceToNdJsonOption(env.bot.gameStateStream(wf, pov.color, me))
|
|
}
|
|
|
|
def move(me: UserModel, pov: Pov, uci: String, offeringDraw: Option[Boolean]) =
|
|
env.bot.player(pov, me, uci, offeringDraw) pipe toResult
|
|
|
|
def command(me: UserModel, cmd: String)(
|
|
as: (String, UserModel) => (Pov => Fu[Result]) => Fu[Result]
|
|
)(implicit req: Request[_]): Fu[Result] =
|
|
cmd.split('/') match {
|
|
case Array("game", id, "chat") =>
|
|
as(id, me) { pov =>
|
|
env.bot.form.chat
|
|
.bindFromRequest()
|
|
.fold(
|
|
jsonFormErrorDefaultLang,
|
|
res => env.bot.player.chat(pov.gameId, me, res) inject jsonOkResult
|
|
) pipe catchClientError
|
|
}
|
|
case Array("game", id, "abort") =>
|
|
as(id, me) { pov =>
|
|
env.bot.player.abort(pov) pipe toResult
|
|
}
|
|
case Array("game", id, "resign") =>
|
|
as(id, me) { pov =>
|
|
env.bot.player.resign(pov) pipe toResult
|
|
}
|
|
case Array("game", id, "draw", bool) =>
|
|
as(id, me) { pov =>
|
|
fuccess(env.bot.player.setDraw(pov, lila.common.Form.trueish(bool))) pipe toResult
|
|
}
|
|
case Array("game", id, "takeback", bool) =>
|
|
as(id, me) { pov =>
|
|
fuccess(env.bot.player.setTakeback(pov, lila.common.Form.trueish(bool))) pipe toResult
|
|
}
|
|
case Array("game", id, "claim-victory") =>
|
|
as(id, me) { pov =>
|
|
env.bot.player.claimVictory(pov) pipe toResult
|
|
}
|
|
case _ => notFoundJson("No such command")
|
|
}
|
|
}
|
|
|
|
def boardCommandGet(cmd: String) =
|
|
ScopedBody(_.Board.Play) { implicit req => me =>
|
|
cmd.split('/') match {
|
|
case Array("game", id, "chat") => WithPovAsBoard(id, me)(getChat)
|
|
case _ => notFoundJson("No such command")
|
|
}
|
|
}
|
|
|
|
def botCommandGet(cmd: String) =
|
|
ScopedBody(_.Bot.Play) { implicit req => me =>
|
|
cmd.split('/') match {
|
|
case Array("game", id, "chat") => WithPovAsBot(id, me)(getChat)
|
|
case _ => notFoundJson("No such command")
|
|
}
|
|
}
|
|
|
|
private def getChat(pov: Pov) =
|
|
env.chat.api.userChat.find(lila.chat.Chat.Id(pov.game.id)) map lila.chat.JsonView.boardApi map JsonOk
|
|
|
|
// utils
|
|
|
|
private def toResult(f: Funit): Fu[Result] = catchClientError(f inject jsonOkResult)
|
|
private def catchClientError(f: Fu[Result]): Fu[Result] =
|
|
f recover { case e: lila.round.BenignError =>
|
|
BadRequest(jsonError(e.getMessage))
|
|
}
|
|
|
|
private def WithPovAsBot(anyId: String, me: lila.user.User)(f: Pov => Fu[Result]) =
|
|
WithPov(anyId, me) { pov =>
|
|
if (me.noBot)
|
|
BadRequest(
|
|
jsonError(
|
|
"This endpoint can only be used with a Bot account. See https://lichess.org/api#operation/botAccountUpgrade"
|
|
)
|
|
).fuccess
|
|
else if (!lila.game.Game.isBotCompatible(pov.game))
|
|
BadRequest(jsonError("This game cannot be played with the Bot API.")).fuccess
|
|
else f(pov)
|
|
}
|
|
|
|
private def WithPovAsBoard(anyId: String, me: lila.user.User)(f: Pov => Fu[Result]) =
|
|
WithPov(anyId, me) { pov =>
|
|
if (me.isBot) notForBotAccounts.fuccess
|
|
else if (!lila.game.Game.isBoardCompatible(pov.game))
|
|
BadRequest(jsonError("This game cannot be played with the Board API.")).fuccess
|
|
else f(pov)
|
|
}
|
|
|
|
private def WithPov(anyId: String, me: lila.user.User)(f: Pov => Fu[Result]) =
|
|
env.round.proxyRepo.game(lila.game.Game takeGameId anyId) flatMap {
|
|
case None => NotFound(jsonError("No such game")).fuccess
|
|
case Some(game) =>
|
|
Pov(game, me) match {
|
|
case None => NotFound(jsonError("Not your game")).fuccess
|
|
case Some(pov) => f(pov)
|
|
}
|
|
}
|
|
|
|
def botOnline =
|
|
Open { implicit ctx =>
|
|
env.user.repo.botsByIds(env.bot.onlineApiUsers.get) map { users =>
|
|
Ok(views.html.user.bots(users))
|
|
}
|
|
}
|
|
|
|
def botOnlineApi =
|
|
Action { implicit req =>
|
|
apiC.jsonStream {
|
|
env.user.repo
|
|
.botsByIdsCursor(env.bot.onlineApiUsers.get)
|
|
.documentSource(getInt("nb", req) | Int.MaxValue)
|
|
.throttle(50, 1 second)
|
|
.map { env.user.jsonView.full(_, withOnline = false, withRating = true) }
|
|
}
|
|
}
|
|
}
|