2013-12-30 19:00:56 -07:00
|
|
|
package controllers
|
|
|
|
|
2015-01-17 04:35:54 -07:00
|
|
|
import play.api.libs.json._
|
2017-01-15 05:26:08 -07:00
|
|
|
import play.api.mvc._
|
2016-07-10 02:08:02 -06:00
|
|
|
import scala.concurrent.duration._
|
2013-12-30 19:00:56 -07:00
|
|
|
|
2017-01-22 13:57:12 -07:00
|
|
|
import lila.api.Context
|
2013-12-30 19:00:56 -07:00
|
|
|
import lila.app._
|
2017-02-15 17:53:15 -07:00
|
|
|
import lila.common.{ HTTPRequest, IpAddress }
|
2013-12-30 19:00:56 -07:00
|
|
|
|
|
|
|
object Api extends LilaController {
|
|
|
|
|
2014-01-07 18:43:20 -07:00
|
|
|
private val userApi = Env.api.userApi
|
|
|
|
private val gameApi = Env.api.gameApi
|
|
|
|
|
2016-08-31 15:59:31 -06:00
|
|
|
private implicit val limitedDefault = ornicar.scalalib.Zero.instance[ApiResult](Limited)
|
|
|
|
|
2016-08-02 04:43:13 -06:00
|
|
|
private lazy val apiStatusResponse = {
|
2015-01-24 03:36:05 -07:00
|
|
|
val api = lila.api.Mobile.Api
|
2015-01-17 04:35:54 -07:00
|
|
|
Ok(Json.obj(
|
2015-01-24 03:36:05 -07:00
|
|
|
"api" -> Json.obj(
|
2016-07-15 11:41:48 -06:00
|
|
|
"current" -> api.currentVersion.value,
|
2015-01-24 03:36:05 -07:00
|
|
|
"olds" -> api.oldVersions.map { old =>
|
|
|
|
Json.obj(
|
2016-07-15 11:41:48 -06:00
|
|
|
"version" -> old.version.value,
|
2015-01-24 03:36:05 -07:00
|
|
|
"deprecatedAt" -> old.deprecatedAt,
|
2017-02-14 08:34:07 -07:00
|
|
|
"unsupportedAt" -> old.unsupportedAt
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2015-01-17 04:35:54 -07:00
|
|
|
)) as JSON
|
|
|
|
}
|
|
|
|
|
2016-08-02 04:43:13 -06:00
|
|
|
val status = Action { req =>
|
|
|
|
apiStatusResponse
|
|
|
|
}
|
|
|
|
|
2016-08-02 04:40:10 -06:00
|
|
|
def user(name: String) = ApiRequest { implicit ctx =>
|
|
|
|
userApi one name map toApiResult
|
2013-12-30 19:00:56 -07:00
|
|
|
}
|
2014-01-07 18:43:20 -07:00
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val UsersRateLimitGlobal = new lila.memo.RateLimit[String](
|
2016-08-11 16:41:50 -06:00
|
|
|
credits = 1000,
|
|
|
|
duration = 1 minute,
|
2016-09-01 15:54:43 -06:00
|
|
|
name = "team users API global",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "team_users.api.global"
|
|
|
|
)
|
2016-08-11 16:41:50 -06:00
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val UsersRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
2016-08-11 16:41:50 -06:00
|
|
|
credits = 1000,
|
|
|
|
duration = 10 minutes,
|
2016-09-01 15:54:43 -06:00
|
|
|
name = "team users API per IP",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "team_users.api.ip"
|
|
|
|
)
|
2016-08-11 16:41:50 -06:00
|
|
|
|
2016-08-02 04:40:10 -06:00
|
|
|
def users = ApiRequest { implicit ctx =>
|
2016-08-31 05:00:39 -06:00
|
|
|
val page = (getInt("page") | 1) atLeast 1 atMost 50
|
|
|
|
val nb = (getInt("nb") | 10) atLeast 1 atMost 50
|
2016-08-11 16:41:50 -06:00
|
|
|
val cost = page * nb + 10
|
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
2016-09-02 06:07:00 -06:00
|
|
|
UsersRateLimitPerIP(ip, cost = cost) {
|
2017-02-15 17:53:15 -07:00
|
|
|
UsersRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
2016-08-11 16:46:05 -06:00
|
|
|
lila.mon.api.teamUsers.cost(cost)
|
2016-08-11 16:41:50 -06:00
|
|
|
(get("team") ?? Env.team.api.team).flatMap {
|
|
|
|
_ ?? { team =>
|
|
|
|
Env.team.pager(team, page, nb) map userApi.pager map some
|
|
|
|
}
|
|
|
|
} map toApiResult
|
2016-08-11 16:32:39 -06:00
|
|
|
}
|
2016-08-11 16:41:50 -06:00
|
|
|
}
|
2014-01-08 17:06:20 -07:00
|
|
|
}
|
|
|
|
|
2017-01-22 14:21:57 -07:00
|
|
|
def usersByIds = OpenBody(parse.tolerantText) { implicit ctx =>
|
|
|
|
val usernames = ctx.body.body.split(',').take(300).toList
|
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
|
|
|
val cost = usernames.size / 4
|
|
|
|
UsersRateLimitPerIP(ip, cost = cost) {
|
2017-02-15 17:53:15 -07:00
|
|
|
UsersRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
2017-01-22 14:21:57 -07:00
|
|
|
lila.mon.api.users.cost(1)
|
|
|
|
lila.user.UserRepo nameds usernames map {
|
|
|
|
_.map { Env.user.jsonView(_, none) }
|
|
|
|
} map toApiResult map toHttp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 08:20:03 -07:00
|
|
|
def usersStatus = ApiRequest { implicit ctx =>
|
|
|
|
val ids = get("ids").??(_.split(',').take(40).toList map lila.user.User.normalize)
|
|
|
|
Env.user.lightUserApi asyncMany ids dmap (_.flatten) map { users =>
|
|
|
|
val actualIds = users.map(_.id)
|
|
|
|
val onlineIds = Env.user.onlineUserIdMemo intersect actualIds
|
2017-02-17 02:34:32 -07:00
|
|
|
val playingIds = Env.relation.online.playing intersect actualIds
|
2017-01-29 08:20:03 -07:00
|
|
|
toApiResult {
|
|
|
|
users.map { u =>
|
|
|
|
lila.common.LightUser.lightUserWrites.writes(u) ++ Json.obj(
|
|
|
|
"online" -> onlineIds.contains(u.id),
|
2017-02-14 08:34:07 -07:00
|
|
|
"playing" -> playingIds.contains(u.id)
|
|
|
|
)
|
2017-01-29 08:20:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
2016-07-29 09:57:48 -06:00
|
|
|
credits = 10 * 1000,
|
|
|
|
duration = 10 minutes,
|
2016-09-01 15:54:43 -06:00
|
|
|
name = "user games API per IP",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "user_games.api.ip"
|
|
|
|
)
|
2016-07-30 15:46:37 -06:00
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val UserGamesRateLimitPerUA = new lila.memo.RateLimit[String](
|
2016-08-01 06:38:07 -06:00
|
|
|
credits = 10 * 1000,
|
2016-08-01 07:11:18 -06:00
|
|
|
duration = 5 minutes,
|
2016-09-01 15:54:43 -06:00
|
|
|
name = "user games API per UA",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "user_games.api.ua"
|
|
|
|
)
|
2016-08-01 06:38:07 -06:00
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val UserGamesRateLimitGlobal = new lila.memo.RateLimit[String](
|
2017-04-06 06:14:39 -06:00
|
|
|
credits = 15 * 1000,
|
|
|
|
duration = 2 minute,
|
2016-09-01 15:54:43 -06:00
|
|
|
name = "user games API global",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "user_games.api.global"
|
|
|
|
)
|
2016-07-08 17:32:54 -06:00
|
|
|
|
2016-08-02 04:40:10 -06:00
|
|
|
def userGames(name: String) = ApiRequest { implicit ctx =>
|
2016-08-31 05:00:39 -06:00
|
|
|
val page = (getInt("page") | 1) atLeast 1 atMost 200
|
|
|
|
val nb = (getInt("nb") | 10) atLeast 1 atMost 100
|
2016-07-29 09:57:48 -06:00
|
|
|
val cost = page * nb + 10
|
2016-08-02 03:00:41 -06:00
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
2017-01-15 05:56:49 -07:00
|
|
|
UserGamesRateLimitPerIP(ip, cost = cost) {
|
2017-02-15 17:53:15 -07:00
|
|
|
UserGamesRateLimitPerUA(~HTTPRequest.userAgent(ctx.req), cost = cost, msg = ip.value) {
|
|
|
|
UserGamesRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
2016-08-11 16:46:05 -06:00
|
|
|
lila.mon.api.userGames.cost(cost)
|
2016-08-01 06:38:07 -06:00
|
|
|
lila.user.UserRepo named name flatMap {
|
|
|
|
_ ?? { user =>
|
|
|
|
gameApi.byUser(
|
|
|
|
user = user,
|
|
|
|
rated = getBoolOpt("rated"),
|
2016-08-11 13:56:05 -06:00
|
|
|
playing = getBoolOpt("playing"),
|
2016-08-01 06:38:07 -06:00
|
|
|
analysed = getBoolOpt("analysed"),
|
|
|
|
withAnalysis = getBool("with_analysis"),
|
|
|
|
withMoves = getBool("with_moves"),
|
|
|
|
withOpening = getBool("with_opening"),
|
|
|
|
withMoveTimes = getBool("with_movetimes"),
|
|
|
|
token = get("token"),
|
|
|
|
nb = nb,
|
|
|
|
page = page
|
|
|
|
) map some
|
|
|
|
}
|
2016-08-02 04:40:10 -06:00
|
|
|
} map toApiResult
|
2016-07-08 17:32:54 -06:00
|
|
|
}
|
2016-01-21 23:45:07 -07:00
|
|
|
}
|
|
|
|
}
|
2013-12-30 19:00:56 -07:00
|
|
|
}
|
|
|
|
|
2017-02-15 17:53:15 -07:00
|
|
|
private val GameRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
2017-01-15 05:56:49 -07:00
|
|
|
credits = 100,
|
|
|
|
duration = 1 minute,
|
|
|
|
name = "game API per IP",
|
2017-02-14 08:34:07 -07:00
|
|
|
key = "game.api.one.ip"
|
|
|
|
)
|
2016-08-31 15:59:31 -06:00
|
|
|
|
2016-08-02 04:40:10 -06:00
|
|
|
def game(id: String) = ApiRequest { implicit ctx =>
|
2016-08-31 15:59:31 -06:00
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
2017-01-15 05:56:49 -07:00
|
|
|
GameRateLimitPerIP(ip, cost = 1) {
|
2016-08-31 15:59:31 -06:00
|
|
|
lila.mon.api.game.cost(1)
|
|
|
|
gameApi.one(
|
|
|
|
id = id take lila.game.Game.gameIdSize,
|
|
|
|
withAnalysis = getBool("with_analysis"),
|
|
|
|
withMoves = getBool("with_moves"),
|
|
|
|
withOpening = getBool("with_opening"),
|
|
|
|
withFens = getBool("with_fens"),
|
|
|
|
withMoveTimes = getBool("with_movetimes"),
|
2017-02-14 08:34:07 -07:00
|
|
|
token = get("token")
|
|
|
|
) map toApiResult
|
2016-08-31 15:59:31 -06:00
|
|
|
}
|
2014-06-06 03:08:43 -06:00
|
|
|
}
|
|
|
|
|
2017-01-22 13:57:12 -07:00
|
|
|
def games = OpenBody(parse.tolerantText) { implicit ctx =>
|
|
|
|
val gameIds = ctx.body.body.split(',').take(300)
|
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
|
|
|
GameRateLimitPerIP(ip, cost = gameIds.size / 4) {
|
|
|
|
lila.mon.api.game.cost(1)
|
|
|
|
gameApi.many(
|
|
|
|
ids = gameIds,
|
2017-02-14 08:34:07 -07:00
|
|
|
withMoves = getBool("with_moves")
|
|
|
|
) map toApiResult map toHttp
|
2017-01-22 13:57:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-06 06:14:39 -06:00
|
|
|
def gamesVs(u1: String, u2: String) = ApiRequest { implicit ctx =>
|
|
|
|
val page = (getInt("page") | 1) atLeast 1 atMost 200
|
|
|
|
val nb = (getInt("nb") | 10) atLeast 1 atMost 100
|
|
|
|
val cost = page * nb * 2 + 10
|
|
|
|
val ip = HTTPRequest lastRemoteAddress ctx.req
|
|
|
|
UserGamesRateLimitPerIP(ip, cost = cost) {
|
|
|
|
UserGamesRateLimitPerUA(~HTTPRequest.userAgent(ctx.req), cost = cost, msg = ip.value) {
|
|
|
|
UserGamesRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
|
|
|
lila.mon.api.userGames.cost(cost)
|
|
|
|
for {
|
|
|
|
usersO <- lila.user.UserRepo.pair(
|
|
|
|
lila.user.User.normalize(u1),
|
|
|
|
lila.user.User.normalize(u2)
|
|
|
|
)
|
|
|
|
res <- usersO.?? { users =>
|
|
|
|
gameApi.byUsersVs(
|
|
|
|
users = users,
|
|
|
|
rated = getBoolOpt("rated"),
|
|
|
|
playing = getBoolOpt("playing"),
|
|
|
|
analysed = getBoolOpt("analysed"),
|
|
|
|
withAnalysis = getBool("with_analysis"),
|
|
|
|
withMoves = getBool("with_moves"),
|
|
|
|
withOpening = getBool("with_opening"),
|
|
|
|
withMoveTimes = getBool("with_movetimes"),
|
|
|
|
nb = nb,
|
|
|
|
page = page
|
|
|
|
) map some
|
|
|
|
}
|
|
|
|
} yield toApiResult(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-15 05:10:53 -06:00
|
|
|
def currentTournaments = ApiRequest { implicit ctx =>
|
2017-01-30 04:36:29 -07:00
|
|
|
Env.tournament.api.fetchVisibleTournaments flatMap
|
2016-08-15 05:10:53 -06:00
|
|
|
Env.tournament.scheduleJsonView.apply map Data.apply
|
|
|
|
}
|
|
|
|
|
|
|
|
def tournament(id: String) = ApiRequest { implicit ctx =>
|
2016-08-31 05:00:39 -06:00
|
|
|
val page = (getInt("page") | 1) atLeast 1 atMost 200
|
2016-08-15 05:10:53 -06:00
|
|
|
lila.tournament.TournamentRepo byId id flatMap {
|
|
|
|
_ ?? { tour =>
|
|
|
|
Env.tournament.jsonView(tour, page.some, none, none, none) map some
|
|
|
|
}
|
|
|
|
} map toApiResult
|
|
|
|
}
|
|
|
|
|
2016-12-04 17:58:51 -07:00
|
|
|
def gameStream = Action(parse.tolerantText) { req =>
|
|
|
|
val userIds = req.body.split(',').take(300).toSet map lila.user.User.normalize
|
2016-10-30 17:56:47 -06:00
|
|
|
Ok.chunked(Env.game.stream.startedByUserIds(userIds))
|
2016-10-30 17:21:48 -06:00
|
|
|
}
|
|
|
|
|
2016-08-02 04:40:10 -06:00
|
|
|
sealed trait ApiResult
|
|
|
|
case class Data(json: JsValue) extends ApiResult
|
|
|
|
case object NoData extends ApiResult
|
|
|
|
case object Limited extends ApiResult
|
2017-01-22 13:57:12 -07:00
|
|
|
def toApiResult(json: Option[JsValue]): ApiResult = json.fold[ApiResult](NoData)(Data.apply)
|
|
|
|
def toApiResult(json: Seq[JsValue]): ApiResult = Data(JsArray(json))
|
|
|
|
|
|
|
|
private def ApiRequest(js: Context => Fu[ApiResult]) = Open { implicit ctx =>
|
|
|
|
js(ctx) map toHttp
|
|
|
|
}
|
|
|
|
|
|
|
|
private def toHttp(result: ApiResult)(implicit ctx: Context): Result = result match {
|
|
|
|
case Limited => TooManyRequest(jsonError("Try again later"))
|
2017-02-14 08:34:07 -07:00
|
|
|
case NoData => NotFound
|
2017-01-22 13:57:12 -07:00
|
|
|
case Data(json) => get("callback") match {
|
2017-02-14 08:34:07 -07:00
|
|
|
case None => Ok(json) as JSON
|
2017-01-22 13:57:12 -07:00
|
|
|
case Some(callback) => Ok(s"$callback($json)") as JAVASCRIPT
|
2013-12-30 19:00:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|