Skip to content

Commit

Permalink
YOLO merge from testy-bot-studio
Browse files Browse the repository at this point in the history
  • Loading branch information
schlawg committed Feb 19, 2025
2 parents f547e72 + 63c34dc commit 547104d
Show file tree
Hide file tree
Showing 140 changed files with 9,494 additions and 1,096 deletions.
2 changes: 2 additions & 0 deletions app/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class Env(
given scheduler: Scheduler = system.scheduler
given RateLimit = net.rateLimit
given NetDomain = net.domain
given getFile: (String => java.io.File) = environment.getFile

// wire all the lila modules in the right order
val i18n: lila.i18n.Env.type = lila.i18n.Env
Expand Down Expand Up @@ -98,6 +99,7 @@ final class Env(
val bot: lila.bot.Env = wire[lila.bot.Env]
val storm: lila.storm.Env = wire[lila.storm.Env]
val racer: lila.racer.Env = wire[lila.racer.Env]
val local: lila.local.Env = wire[lila.local.Env]
val opening: lila.opening.Env = wire[lila.opening.Env]
val tutor: lila.tutor.Env = wire[lila.tutor.Env]
val recap: lila.recap.Env = wire[lila.recap.Env]
Expand Down
1 change: 1 addition & 0 deletions app/LilaComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ final class LilaComponents(
lazy val irwin: Irwin = wire[Irwin]
lazy val learn: Learn = wire[Learn]
lazy val lobby: Lobby = wire[Lobby]
lazy val localPlay: Local = wire[Local]
lazy val main: Main = wire[Main]
lazy val msg: Msg = wire[Msg]
lazy val mod: Mod = wire[Mod]
Expand Down
125 changes: 125 additions & 0 deletions app/controllers/Local.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package controllers

import play.api.libs.json.*
import play.api.i18n.Lang
import play.api.mvc.*
import play.api.data.*
import play.api.data.Forms.*
import views.*

import lila.app.{ given, * }
import lila.common.Json.given
import lila.user.User
import lila.rating.{ Perf, PerfType }
import lila.security.Permission
import lila.local.{ GameSetup, AssetType }

final class Local(env: Env) extends LilaController(env):
def index = Open:
for
bots <- env.local.repo.getLatestBots()
page <- renderPage(indexPage(bots, none))
yield Ok(page).enforceCrossSiteIsolation.withHeaders("Service-Worker-Allowed" -> "/")

def bots = Open:
env.local.repo
.getLatestBots()
.map: bots =>
JsonOk(Json.obj("bots" -> bots))

def assetKeys = Open: // for service worker
JsonOk(env.local.api.assetKeys)

def devIndex = Auth: _ ?=>
for
bots <- env.local.repo.getLatestBots()
assets <- devGetAssets
page <- renderPage(indexPage(bots, assets.some))
yield Ok(page).enforceCrossSiteIsolation.withHeaders("Service-Worker-Allowed" -> "/")

def devAssets = Auth: ctx ?=>
devGetAssets.map(JsonOk)

def devBotHistory(botId: Option[String]) = Auth: _ ?=>
env.local.repo
.getVersions(botId.map(UserId.apply))
.map: history =>
JsonOk(Json.obj("bots" -> history))

def devPostBot = SecureBody(parse.json)(_.BotEditor) { ctx ?=> me ?=>
ctx.body.body
.validate[JsObject]
.fold(
err => BadRequest(Json.obj("error" -> err.toString)),
bot =>
env.local.repo
.putBot(bot, me.userId)
.map: updatedBot =>
JsonOk(updatedBot)
)
}

def devNameAsset(key: String, name: String) = Secure(_.BotEditor): _ ?=>
env.local.repo
.nameAsset(none, key, name, none)
.flatMap(_ => devGetAssets.map(JsonOk))

def devDeleteAsset(key: String) = Secure(_.BotEditor): _ ?=>
env.local.repo
.deleteAsset(key)
.flatMap(_ => devGetAssets.map(JsonOk))

def devPostAsset(notAString: String, key: String) = SecureBody(parse.multipartFormData)(_.BotEditor) {
ctx ?=>
val tpe: AssetType = notAString.asInstanceOf[AssetType]
val author: Option[String] = ctx.body.body.dataParts.get("author").flatMap(_.headOption)
val name = ctx.body.body.dataParts.get("name").flatMap(_.headOption).getOrElse(key)
ctx.body.body
.file("file")
.map: file =>
env.local.api
.storeAsset(tpe, key, file)
.flatMap:
case Left(error) => InternalServerError(Json.obj("error" -> error.toString)).as(JSON)
case Right(assets) =>
env.local.repo
.nameAsset(tpe.some, key, name, author)
.flatMap(_ => (JsonOk(Json.obj("key" -> key, "name" -> name))))
.getOrElse(fuccess(BadRequest(Json.obj("error" -> "missing file")).as(JSON)))
}

private def indexPage(bots: JsArray, devAssets: Option[JsObject] = none)(using
ctx: Context
) =
given setupFormat: Format[GameSetup] = Json.format[GameSetup]
views.local.index(
Json
.obj("pref" -> pref, "bots" -> bots)
.add("assets", devAssets)
.add("userId", ctx.me.map(_.userId))
.add("username", ctx.me.map(_.username))
.add("canPost", isGrantedOpt(_.BotEditor)),
if devAssets.isDefined then "local.dev" else "local"
)

private def devGetAssets =
env.local.repo.getAssets.map: m =>
JsObject:
env.local.api.assetKeys
.as[JsObject]
.fields
.collect:
case (category, JsArray(keys)) =>
category -> JsArray:
keys.collect:
case JsString(key) if m.contains(key) =>
Json.obj("key" -> key, "name" -> m(key))

private def pref(using ctx: Context) =
lila.pref.JsonView
.write(ctx.pref, false)
.add("animationDuration", ctx.pref.animationMillis.some)
.add("enablePremove", ctx.pref.premove.some)

private def optTrue(s: Option[String]) =
s.exists(v => v == "" || v == "1" || v == "true")
2 changes: 1 addition & 1 deletion app/controllers/Round.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ final class Round(
jsChat <- chat.flatMap(_.game).map(_.chat).soFu(lila.chat.JsonView.asyncLines)
yield Ok(data.add("chat", jsChat)).noCache
)
yield res
yield res.enforceCrossSiteIsolation

def player(fullId: GameFullId) = Open:
env.round.proxyRepo
Expand Down
1 change: 1 addition & 0 deletions app/views/game/widgets.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ object widgets:

private val separator = ""

// mirrored html generation in file://../../../ui/user/src/user.ts
def apply(
games: Seq[Game],
notes: Map[GameId, String] = Map(),
Expand Down
2 changes: 2 additions & 0 deletions app/views/ui.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ val challenge = lila.challenge.ui.ChallengeUi(helpers)

val dev = lila.web.ui.DevUi(helpers)(mod.ui.menu)

val local = lila.local.ui.LocalUi(helpers)

def mobile(p: lila.cms.CmsPage.Render)(using Context) =
lila.web.ui.mobile(helpers)(cms.render(p))

Expand Down
2 changes: 1 addition & 1 deletion bin/schlawg-dev
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
journalctl --user -fu lila -o cat | grep -E -v fishnet &
JOURNALCTL_PID=$!

# l=/ui-build
# l=https://schlawg.org/console
"$(dirname "${BASH_SOURCE[0]:-$0}")/../ui/build" -${1:-'wd'} &
UI_BUILD_PID=$!

Expand Down
7 changes: 6 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ lazy val modules = Seq(
// and then the smaller ones
pool, lobby, relation, tv, coordinate, feed, history, recap,
shutup, appeal, irc, explorer, learn, event, coach,
practice, evalCache, irwin, bot, racer, cms, i18n,
practice, evalCache, irwin, bot, racer, cms, i18n, local,
socket, bookmark, studySearch, gameSearch, forumSearch, teamSearch,
)

Expand Down Expand Up @@ -149,6 +149,11 @@ lazy val racer = module("racer",
Seq()
)

lazy val local = module("local",
Seq(db, memo, ui, pref),
Seq()
)

lazy val video = module("video",
Seq(memo, ui),
macwire.bundle
Expand Down
1 change: 1 addition & 0 deletions conf/base.conf
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ insight {
learn {
collection.progress = learn_progress
}
local.asset_path = "public/lifat/bots"
kaladin.enabled = false
zulip {
domain = ""
Expand Down
12 changes: 12 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,18 @@ GET /streamer/:username controllers.Streamer.show(username: UserS
GET /streamer/:username/redirect controllers.Streamer.redirect(username: UserStr)
POST /streamer/:username/check controllers.Streamer.checkOnline(username: UserStr)

# Private Play

GET /local controllers.Local.index
GET /local/bots controllers.Local.bots
GET /local/assets controllers.Local.assetKeys
GET /local/dev controllers.Local.devIndex
GET /local/dev/history controllers.Local.devBotHistory(id: Option[String])
POST /local/dev/bot controllers.Local.devPostBot
GET /local/dev/assets controllers.Local.devAssets
POST /local/dev/asset/$tpe<sound|image|book>/$key<\w{12}(\.\w{2,4})?> controllers.Local.devPostAsset(tpe: String, key: String)
POST /local/dev/asset/mv/$key<\w{12}(\.\w{2,4})?>/:name controllers.Local.devNameAsset(key: String, name: String)

# Round
GET /$gameId<\w{8}> controllers.Round.watcher(gameId: GameId, color: Color = Color.white)
GET /$gameId<\w{8}>/$color<white|black> controllers.Round.watcher(gameId: GameId, color: Color)
Expand Down
4 changes: 3 additions & 1 deletion modules/core/src/main/perm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ enum Permission(val key: String, val alsoGrants: List[Permission], val name: Str
case ApiHog extends Permission("API_HOG", "API hog")
case ApiChallengeAdmin extends Permission("API_CHALLENGE_ADMIN", "API Challenge admin")
case LichessTeam extends Permission("LICHESS_TEAM", List(Beta), "Lichess team")
case BotEditor extends Permission("BOT_EDITOR", "Bot editor")
case TimeoutMod
extends Permission(
"TIMEOUT_MOD",
Expand Down Expand Up @@ -188,6 +189,7 @@ enum Permission(val key: String, val alsoGrants: List[Permission], val name: Str
extends Permission(
"ADMIN",
List(
BotEditor,
LichessTeam,
UserSearch,
PrizeBan,
Expand Down Expand Up @@ -243,7 +245,7 @@ object Permission:
val all: Set[Permission] = values.toSet

val nonModPermissions: Set[Permission] =
Set(Beta, Coach, Teacher, Developer, Verified, ContentTeam, BroadcastTeam, ApiHog)
Set(Beta, Coach, Teacher, Developer, Verified, ContentTeam, BroadcastTeam, ApiHog, BotEditor)

val modPermissions: Set[Permission] = all.diff(nonModPermissions)

Expand Down
5 changes: 2 additions & 3 deletions modules/forum/src/main/ForumForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@ final private[forum] class ForumForm(
single("categ" -> nonEmptyText.into[ForumCategId])

private def userTextMapping(inOwnTeam: Boolean, previousText: Option[String] = None)(using me: Me) =
cleanText(minLength = 3, 20_000)
cleanText(minLength = 3, 10_000_000) // bot move dumps
.verifying(
"You have reached the daily maximum for links in forum posts.",
t => inOwnTeam || promotion.test(me, t, previousText)
)

val diagnostic = Form(single("text" -> nonEmptyText(maxLength = 100_000)))
val diagnostic = Form(single("text" -> nonEmptyText(maxLength = 10_000_000))) // bot move dumps

object ForumForm:

Expand Down
28 changes: 28 additions & 0 deletions modules/local/src/main/Env.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lila.local

import com.softwaremill.macwire.*
import play.api.Configuration

import lila.common.autoconfig.{ *, given }
import lila.core.config.*

@Module
final private class LocalConfig(
@ConfigName("asset_path") val assetPath: String
)

@Module
final class Env(
appConfig: Configuration,
db: lila.db.Db,
getFile: (String => java.io.File)
)(using
Executor,
akka.stream.Materializer
)(using mode: play.api.Mode, scheduler: Scheduler):

private val config: LocalConfig = appConfig.get[LocalConfig]("local")(AutoConfig.loader)

val repo = LocalRepo(db(CollName("local_bots")), db(CollName("local_assets")))

val api: LocalApi = wire[LocalApi]
56 changes: 56 additions & 0 deletions modules/local/src/main/LocalApi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lila.local

import java.nio.file.{ Files as NioFiles, Paths }
import play.api.libs.json.*
import play.api.libs.Files
import play.api.mvc.*
import akka.stream.scaladsl.{ FileIO, Source }
import akka.util.ByteString

// this stuff is for bot devs

final private class LocalApi(config: LocalConfig, repo: LocalRepo, getFile: (String => java.io.File))(using
Executor,
akka.stream.Materializer
):

@volatile private var cachedAssets: Option[JsObject] = None

def storeAsset(
tpe: AssetType,
name: String,
file: MultipartFormData.FilePart[Files.TemporaryFile]
): Fu[Either[String, JsObject]] =
FileIO
.fromPath(file.ref.path)
.runWith(FileIO.toPath(getFile(s"public/lifat/bots/${tpe}/$name").toPath))
.map: result =>
if result.wasSuccessful then Right(updateAssets)
else Left(s"Error uploading asset $tpe $name")
.recover:
case e: Exception => Left(s"Exception: ${e.getMessage}")

def assetKeys: JsObject = cachedAssets.getOrElse(updateAssets)

private def listFiles(tpe: String, ext: String): List[String] =
val path = getFile(s"public/lifat/bots/${tpe}")
if !path.exists() then
NioFiles.createDirectories(path.toPath)
Nil
else
path
.listFiles()
.toList
.map(_.getName)
.filter(_.endsWith(s".${ext}"))

def updateAssets: JsObject =
val newAssets = Json.obj(
"image" -> listFiles("image", "webp"),
"net" -> listFiles("net", "pb"),
"sound" -> listFiles("sound", "mp3"),
"book" -> listFiles("book", "png")
.map(_.dropRight(4))
)
cachedAssets = newAssets.some
newAssets
Loading

0 comments on commit 547104d

Please sign in to comment.