omegga 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +17 -0
- package/README.md +39 -10
- package/bin/omegga +1 -1
- package/dist/brickadia/server.js +4 -8
- package/dist/brickadia/server.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/main.js +8 -7
- package/dist/main.js.map +1 -1
- package/dist/omegga/player.js +59 -25
- package/dist/omegga/player.js.map +1 -1
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js +54 -0
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js.map +1 -1
- package/dist/omegga/plugin/plugin_node_unsafe.js +6 -0
- package/dist/omegga/plugin/plugin_node_unsafe.js.map +1 -1
- package/dist/omegga/server.js +35 -1
- package/dist/omegga/server.js.map +1 -1
- package/dist/omegga/wrapper.js +1 -0
- package/dist/omegga/wrapper.js.map +1 -1
- package/dist/plugin.js +1 -2
- package/dist/plugin.js.map +1 -1
- package/dist/softconfig.js +33 -15
- package/dist/softconfig.js.map +1 -1
- package/dist/updater/steam.js +65 -12
- package/dist/updater/steam.js.map +1 -1
- package/dist/util/brick/checkBounds.js +1 -2
- package/dist/util/brick/checkBounds.js.map +1 -1
- package/dist/util/brick/displayName.js +1 -2
- package/dist/util/brick/displayName.js.map +1 -1
- package/dist/util/brick/getBounds.js +1 -2
- package/dist/util/brick/getBounds.js.map +1 -1
- package/dist/util/brick/getBrickSize.js +1 -2
- package/dist/util/brick/getBrickSize.js.map +1 -1
- package/dist/util/brick/getScaleAxis.js +1 -2
- package/dist/util/brick/getScaleAxis.js.map +1 -1
- package/dist/util/brick/setOwnership.js +1 -2
- package/dist/util/brick/setOwnership.js.map +1 -1
- package/dist/webserver/backend/api.js +2 -1
- package/dist/webserver/backend/api.js.map +1 -1
- package/dist/webserver/backend/database.js +3 -1
- package/dist/webserver/backend/database.js.map +1 -1
- package/package.json +3 -2
- package/public/app.html +18 -18
- package/public/assets/IconArrowRight-BuVxhqKX.js +2 -0
- package/public/assets/{IconArrowRight-CA5_cccJ.js.map → IconArrowRight-BuVxhqKX.js.map} +1 -1
- package/public/assets/IconCheck-DOe3u7iH.js +2 -0
- package/public/assets/{IconCheck-Dyq5qyY1.js.map → IconCheck-DOe3u7iH.js.map} +1 -1
- package/public/assets/IconLockOpen-Bs7Tsf6u.js +2 -0
- package/public/assets/{IconLockOpen-D95XLORK.js.map → IconLockOpen-Bs7Tsf6u.js.map} +1 -1
- package/public/assets/IconWorld-B6GK5qpx.js +2 -0
- package/public/assets/{IconWorld-DtARpBKX.js.map → IconWorld-B6GK5qpx.js.map} +1 -1
- package/public/assets/IconWorldPlus-DpfueG8_.js +2 -0
- package/public/assets/{IconWorldPlus-CGBAsLNj.js.map → IconWorldPlus-DpfueG8_.js.map} +1 -1
- package/public/assets/IconX-CvmO0bbE.js +2 -0
- package/public/assets/{IconX-SYtbnYKI.js.map → IconX-CvmO0bbE.js.map} +1 -1
- package/public/assets/app-S3WcjD4u.js +6 -0
- package/public/assets/{app-BtOlgo__.js.map → app-S3WcjD4u.js.map} +1 -1
- package/public/assets/{auth-CvxEWAMc.js → auth-6N37o421.js} +2 -2
- package/public/assets/{auth-CvxEWAMc.js.map → auth-6N37o421.js.map} +1 -1
- package/public/assets/client-BT1QYH0O.js +10 -0
- package/public/assets/{client-C5T7_OK9.js.map → client-BT1QYH0O.js.map} +1 -1
- package/public/assets/index-2cgXLhrt.js +2 -0
- package/public/assets/{index-CRRRLlCc.js.map → index-2cgXLhrt.js.map} +1 -1
- package/public/assets/{index-i4mjW0og.js → index-CrN8I5Az.js} +2 -2
- package/public/assets/index-CrN8I5Az.js.map +1 -0
- package/public/assets/index-CtCtK5BX.js +2 -0
- package/public/assets/{index-DqHVrEeL.js.map → index-CtCtK5BX.js.map} +1 -1
- package/public/assets/index-Cz1Unv84.js +10 -0
- package/public/assets/{index-Bq9C9SqB.js.map → index-Cz1Unv84.js.map} +1 -1
- package/public/assets/index-D0OPOe3j.js +2 -0
- package/public/assets/{index-c0qeY2gs.js.map → index-D0OPOe3j.js.map} +1 -1
- package/public/assets/{index-DYcAgWjJ.js → index-DY750sgZ.js} +2 -2
- package/public/assets/{index-DYcAgWjJ.js.map → index-DY750sgZ.js.map} +1 -1
- package/public/assets/jsx-runtime-BX5r4UiA.js +2 -0
- package/public/assets/jsx-runtime-BX5r4UiA.js.map +1 -0
- package/public/assets/jsx-runtime-DldvXD6N.js +2 -0
- package/public/assets/{jsx-runtime-Bw5QeaCk.js.map → jsx-runtime-DldvXD6N.js.map} +1 -1
- package/public/assets/shared/@components-Bt_lv7Wa.js +2 -0
- package/public/assets/shared/@components-Bt_lv7Wa.js.map +1 -0
- package/public/assets/shared/{@hooks-DfTzYBIL.js → @hooks-Ch_hJYh0.js} +2 -2
- package/public/assets/shared/{@hooks-DfTzYBIL.js.map → @hooks-Ch_hJYh0.js.map} +1 -1
- package/public/assets/shared/{@nanostores_react-HTG3s0lp.js → @nanostores_react-DJPF1Eqx.js} +2 -2
- package/public/assets/shared/{@nanostores_react-HTG3s0lp.js.map → @nanostores_react-DJPF1Eqx.js.map} +1 -1
- package/public/assets/shared/@tabler_icons-react-B4bN8gHc.js +3 -0
- package/public/assets/shared/{@tabler_icons-react--FJWdUou.js.map → @tabler_icons-react-B4bN8gHc.js.map} +1 -1
- package/public/assets/shared/nanostores-BFqqIY_c.js.map +1 -1
- package/public/assets/shared/react-dom-8Rcmh_FG.js +2 -0
- package/public/assets/shared/{react-dom-BznoGTRX.js.map → react-dom-8Rcmh_FG.js.map} +1 -1
- package/public/assets/shared/react-iXLZ_Bod.js +2 -0
- package/public/assets/shared/{react-CR6vSpIu.js.map → react-iXLZ_Bod.js.map} +1 -1
- package/public/assets/shared/react_jsx-runtime-BwhiLcBy.js +2 -0
- package/public/assets/shared/{react_jsx-runtime-C15Zu6_p.js.map → react_jsx-runtime-BwhiLcBy.js.map} +1 -1
- package/public/assets/shared/{wouter-DbmGxXTo.js → wouter-CUC1caxu.js} +2 -2
- package/public/assets/shared/wouter-CUC1caxu.js.map +1 -0
- package/public/auth.html +14 -14
- package/templates/safe-ts/omegga.d.ts +30 -0
- package/public/assets/IconArrowRight-CA5_cccJ.js +0 -7
- package/public/assets/IconCheck-Dyq5qyY1.js +0 -17
- package/public/assets/IconLockOpen-D95XLORK.js +0 -7
- package/public/assets/IconWorld-DtARpBKX.js +0 -72
- package/public/assets/IconWorldPlus-CGBAsLNj.js +0 -182
- package/public/assets/IconX-SYtbnYKI.js +0 -12
- package/public/assets/app-BtOlgo__.js +0 -6
- package/public/assets/client-C5T7_OK9.js +0 -26
- package/public/assets/index-Bq9C9SqB.js +0 -10
- package/public/assets/index-CRRRLlCc.js +0 -10
- package/public/assets/index-DqHVrEeL.js +0 -10
- package/public/assets/index-c0qeY2gs.js +0 -10
- package/public/assets/index-i4mjW0og.js.map +0 -1
- package/public/assets/jsx-runtime-Bw5QeaCk.js +0 -10
- package/public/assets/jsx-runtime-DoEZbXM1.js +0 -2
- package/public/assets/jsx-runtime-DoEZbXM1.js.map +0 -1
- package/public/assets/shared/@components-DVehBp7v.js +0 -2
- package/public/assets/shared/@components-DVehBp7v.js.map +0 -1
- package/public/assets/shared/@tabler_icons-react--FJWdUou.js +0 -29458
- package/public/assets/shared/react-CR6vSpIu.js +0 -2
- package/public/assets/shared/react-dom-BznoGTRX.js +0 -2
- package/public/assets/shared/react_jsx-runtime-C15Zu6_p.js +0 -2
- package/public/assets/shared/wouter-DbmGxXTo.js.map +0 -1
- package/vite.backend.config.mts +0 -46
- package/vite.frontend.config.mts +0 -58
- package/vitest.backend.config.mts +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sources":["../../../src/webserver/backend/api.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { IPluginDocumentation } from '@/plugin';\nimport { steamcmdDownloadGame } from '@/updater';\nimport { getLastSteamUpdateCheck, hasSteamUpdate } from '@/updater/steam';\nimport { VERSION } from '@/version';\nimport Player from '@omegga/player';\nimport { parseLinks, sanitize } from '@util/chat';\nimport { rgbToHex } from '@util/color';\nimport { parseBrickadiaTime } from '@util/time';\nimport * as uuid from '@util/uuid';\nimport express from 'express';\nimport {\n JSONRPCClient,\n JSONRPCServer,\n JSONRPCServerAndClient,\n} from 'json-rpc-2.0';\nimport _ from 'lodash';\nimport Database from './database';\nimport type Webserver from './index';\nimport {\n IFrontendBanEntry,\n IPlayer,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreKickHistory,\n IStoreUser,\n IUserAgo,\n IUserHistory,\n IUserNote,\n OmeggaSocketIo,\n} from './types';\nimport { Plugin } from '@omegga/plugin/interface';\nimport { readBrdbMeta } from '@util/brdb';\n\nexport type OmeggaSocketData = {\n roles: { type: 'role'; name: string }[];\n version: string;\n brickadiaVersion: number | null;\n canLogOut: boolean;\n now: number;\n userless: boolean;\n isSteam: boolean;\n update: {\n canCheck: boolean;\n lastCheck: boolean | null;\n };\n user: {\n username: string;\n isOwner: boolean;\n roles: string[];\n };\n};\n\nexport type GetPlayersRes = {\n pages: number;\n total: number;\n players: (IUserHistory & IUserAgo & { ban?: IFrontendBanEntry })[];\n};\n\nexport type GetPlayerRes = Omit<IUserHistory, 'nameHistory'> & {\n banHistory: (IStoreBanHistory & {\n duration?: number;\n bannerName?: string;\n })[];\n kickHistory: (IStoreKickHistory & {\n kickerName?: string;\n })[];\n notes: IUserNote[];\n nameHistory: {\n name: string;\n displayName: string;\n date: number;\n ago?: number;\n }[];\n} & IUserAgo & {\n isHost: boolean;\n isOnline: boolean;\n currentBan: IFrontendBanEntry | null;\n roles: { name: string; color: string }[];\n };\n\nexport type GetPluginsRes = {\n name: string;\n documentation: IPluginDocumentation;\n path: string;\n isLoaded: boolean;\n isEnabled: boolean;\n}[];\n\nexport type GetPluginRes = {\n name: string;\n format: string;\n info: Record<string, unknown>;\n documentation: IPluginDocumentation;\n config: Record<string, unknown>;\n defaultConfig: Record<string, unknown>;\n objCount: number;\n path: string;\n isLoaded: boolean;\n isEnabled: boolean;\n};\n\nexport type GetUsersRes = {\n pages: number;\n total: number;\n users: (IStoreUser & IUserAgo)[];\n};\n\nexport type HistoryRes = Awaited<ReturnType<Database['getChats']>>;\n\nexport type WorldRevisionsRes = {\n index: number;\n date: number;\n note: string;\n}[];\n\nexport type WorldMetaRes = {\n meta: {\n world: {\n environment: string;\n };\n bundle: {\n type: 'World';\n iD: string;\n name: string;\n version: string;\n tags: string[];\n authors: string[];\n createdAt: string;\n updatedAt: string;\n description: string;\n dependencies: string[];\n };\n };\n owners: {\n id: string;\n name: string;\n display_name: string;\n entity_count: number;\n brick_count: number;\n component_count: number;\n wire_count: number;\n }[];\n};\n\nexport default function (server: Webserver, io: OmeggaSocketIo) {\n const { database, omegga } = server;\n\n // open API is accessible without auth\n const openApi = express.Router();\n const api = express.Router();\n\n // check if this is the first user in the database\n openApi.get('/first', async (req, res) =>\n res.json(await database.isFirstUser()),\n );\n\n // login / create admin user route\n openApi.post('/auth', async (req, res) => {\n // body is username and password\n if (\n typeof req.body !== 'object' ||\n typeof req.body.username !== 'string' ||\n typeof req.body.password !== 'string'\n ) {\n return res.status(422).json({ message: 'invalid body' });\n }\n const { username, password } = req.body;\n\n // username regex\n if (!username.match(/^\\w{0,32}$/)) {\n return res.status(422).json({ message: 'invalid body' });\n }\n\n // if this is the first user, create it as the admin user\n const isFirst = await database.isFirstUser();\n let user;\n if (isFirst) {\n user = await database.createAdminUser(\n username,\n username === '' ? '' : password,\n );\n } else {\n user = await database.authUser(username, password);\n }\n\n if (user) {\n req.session.userId = user._id;\n req.session.save();\n res.status(200).json({});\n } else {\n res.status(404).json({ message: 'no user found' });\n }\n });\n\n // kill a session\n api.get('/logout', (req, res) => {\n req.session.destroy(e => {\n res.status(e ? 500 : 200).json({});\n });\n });\n\n // authentication middleware for api\n api.all('*', (req, _res, next) => {\n (async () => {\n const user = await database.findUserById(req.session.userId);\n if (!user || user.isBanned) return next(new Error('unauthorized'));\n // req.user = user;\n\n next();\n })();\n });\n\n // websocket data\n io.on('connection', socket => {\n // let this user receive messages directed to this user\n socket.join('user:' + socket.data.user._id);\n\n database.getRoles().then(roles => {\n socket.emit('data', {\n roles,\n version: VERSION,\n brickadiaVersion: omegga.version ?? null,\n canLogOut: socket.data.user.username !== '',\n now: Date.now(), // this can be used for the frontend to anticipate drift\n userless: !socket.data.user.username,\n user: {\n username: socket.data.user.username || 'Admin',\n isOwner: socket.data.user.isOwner,\n roles: socket.data.user.roles,\n },\n isSteam: Boolean(omegga.config.__STEAM),\n update: {\n canCheck: omegga.config.__STEAM && !omegga.config.server?.steambeta,\n lastCheck: getLastSteamUpdateCheck()?.result ?? null,\n },\n } satisfies OmeggaSocketData);\n });\n\n // logging for this user in the console for web ui actions\n const usernameText = `[${\n (socket.data.user.username || 'Admin').brightMagenta\n }]`;\n const log = (...args: any[]) => Logger.logp(usernameText, ...args);\n const error = (...args: any[]) => Logger.errorp(usernameText, ...args);\n\n // rpc connection\n const rpcServer = new JSONRPCServer();\n const rpcClient = new JSONRPCClient(async (data: any) => {\n socket.emit('rpc', data);\n });\n const rpc = new JSONRPCServerAndClient(rpcServer, rpcClient);\n socket.on('rpc', data => {\n if (data && typeof data === 'object') {\n try {\n rpc.receiveAndSend(data);\n } catch (e) {\n // silently discard broken rpc requests\n }\n }\n });\n\n // send chat message from web ui\n // TODO: check if this user has a role with permission to chat\n rpc.addMethod('chat', async ([message]: [string]) => {\n if (typeof message !== 'string') return;\n if (message.length > 140) message = message.slice(0, 140);\n\n // create fake user\n const user = {\n name: socket.data.user.username || 'Admin',\n id: socket.data.user.playerId,\n color: 'ff00ff',\n web: true,\n };\n\n // create database entry, send to web ui\n io.to('chat').emit(\n 'chat',\n await database.addChatLog('msg', user, message),\n );\n\n // broadcast to chat\n omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">${user.name}</></>]: ${parseLinks(\n sanitize(message),\n )}\"`,\n );\n\n // broadcast to terminal\n Logger.log(`[${user.name.brightMagenta.underline}]: ${message}`);\n\n return 'ok';\n });\n\n // read recent chat messages\n // TODO: add permission check\n rpc.addMethod('chat.recent', () => {\n return database.getChats({ sameServer: true });\n });\n\n // find chat messages after a certain time\n // TODO: add permission check\n rpc.addMethod(\n 'chat.history',\n ([{ after, before }]: [{ after?: number; before?: number }] = [{}]) => {\n return database.getChats({ after, before });\n },\n );\n\n // see what days chat messages were sent\n // TODO: add permission check\n rpc.addMethod('chat.calendar', () => {\n return database.calendar.years;\n });\n\n // get the list of plugins\n // TODO: add permission check\n rpc.addMethod('plugins.list', () => {\n return _.sortBy(\n omegga.pluginLoader.plugins.map(p => ({\n name: p.getName(),\n documentation: p.getDocumentation(),\n path: p.shortPath,\n isLoaded: p.isLoaded(),\n isEnabled: p.isEnabled(),\n })),\n p => p.name.toLowerCase(),\n ) satisfies GetPluginsRes;\n });\n\n // get information on a specific plugin\n // TODO: add permission check\n rpc.addMethod('plugin.get', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return null;\n\n // get the plugin configs\n const [defaultConfig, config, objCount] = await Promise.all([\n plugin.storage.getDefaultConfig(),\n plugin.storage.getConfig(),\n plugin.storage.count(),\n ]);\n\n return {\n name: plugin.getName(),\n format: (plugin.constructor as typeof Plugin).getFormat(),\n info: plugin.getInfo(),\n documentation: plugin.getDocumentation(),\n config,\n defaultConfig,\n objCount,\n path: plugin.shortPath,\n isLoaded: plugin.isLoaded(),\n isEnabled: plugin.isEnabled(),\n } satisfies GetPluginRes;\n });\n\n // get a paginated list of players\n // TODO: add permission check\n rpc.addMethod(\n 'players.list',\n async ([\n {\n page = 0,\n search = '',\n sort = 'name',\n direction = '1',\n filter = '',\n } = {},\n ]: [\n {\n page?: number;\n search?: string;\n sort?: string;\n direction?: string;\n filter?: string;\n },\n ]) => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n\n // get the ban list\n const now = Date.now();\n\n let limitId;\n\n // if the filter is set to banned players only, limit player results by ids in the current ban list\n if (filter === 'banned') {\n limitId = Object.keys(banList).filter(\n b =>\n banList[b].expires <= banList[b].created ||\n parseBrickadiaTime(banList[b].expires) > Date.now(),\n );\n }\n\n const resp: GetPlayersRes = await database.getPlayers({\n page,\n search,\n sort,\n direction,\n limitId,\n });\n\n for (const player of resp.players) {\n player.seenAgo = now - player.lastSeen;\n player.createdAgo = now - player.created;\n\n const foundBan = banList[player.id];\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n\n // create a clone of the object\n let currentBan: IFrontendBanEntry = {\n ...foundBan,\n // parse the times in the current ban\n created,\n expires,\n duration: expires - created,\n remainingTime: expires - now,\n\n // lookup banner name\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n\n // if the ban is expired, it should not be listed\n if (currentBan.expires < now && currentBan.duration > 0)\n currentBan = null;\n player.ban = currentBan;\n }\n }\n return resp;\n },\n );\n\n // get a specific player's info\n // TODO: add permission check\n rpc.addMethod('player.get', async ([id]: [string]) => {\n const entry: Omit<IUserHistory, 'nameHistory'> & {\n banHistory: (IStoreBanHistory & {\n duration?: number;\n bannerName?: string;\n })[];\n kickHistory: (IStoreKickHistory & { kickerName?: string })[];\n notes: IUserNote[];\n nameHistory: {\n name: string;\n displayName: string;\n date: number;\n ago?: number;\n }[];\n } & IUserAgo = await database.getPlayer(id);\n if (!entry) return null;\n const now = Date.now();\n\n entry.seenAgo = now - entry.lastSeen;\n entry.createdAgo = now - entry.created;\n for (const n of entry.nameHistory) n.ago = now - n.date;\n\n // combine player roles with server roles\n const playerRoles = Player.getRoles(omegga, id) || [];\n const { roles: serverRoles = [] } = omegga.getRoleSetup() ?? {};\n\n // Get banner name and duration from list of bans\n for (const b of entry.banHistory) {\n b.duration = b.expires - b.created;\n // lookup banner name\n b.bannerName = _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.bannerId],\n '',\n );\n }\n\n // Get kicker name from list of kicks\n for (const b of entry.kickHistory) {\n // lookup banner name\n b.kickerName = _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.kickerId],\n '',\n );\n }\n\n const foundBan = (omegga.getBanList() || { banList: {} }).banList[id];\n let currentBan: IFrontendBanEntry = null;\n\n if (foundBan) {\n // parse the times in the current ban\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n // create a clone of the object\n currentBan = {\n ...foundBan,\n created,\n expires,\n duration: expires - created,\n remainingTime: expires - now,\n // lookup banner name\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n\n // if the ban is expired, it should not be listed\n if (currentBan.expires < now && currentBan.duration > 0)\n currentBan = null;\n }\n\n return {\n // database results\n ...entry,\n\n // player is host\n isHost: omegga.getHostId() === id,\n\n isOnline: omegga.players.some(p => p.id === id),\n\n // player's current ban state\n currentBan,\n\n // all player's roles w/ colors\n roles: playerRoles.map(r => {\n // default role color is white\n let color = 'ffffff';\n\n // find the role (if it exists) and get the color\n const role = serverRoles.find(\n sr => sr.name.toLowerCase() === r.toLowerCase(),\n );\n if (role && role.bHasColor) color = rgbToHex(role.color);\n\n return {\n name: r,\n color: color,\n };\n }),\n } satisfies GetPlayerRes;\n });\n\n rpc.addMethod(\n 'player.ban',\n async ([id, duration = -1, reason = 'No Reason']: [\n id: string,\n duration: number,\n reason: string,\n ]) => {\n // validate inputs\n if (typeof duration !== 'number') return false;\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n if (typeof reason !== 'string' || reason.length > 128) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n log(\n 'Banning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n // ban the user\n omegga.writeln(`Chat.Command /Ban \"${id}\" ${duration} \"${reason}\"`);\n let listener;\n\n // wait for the ban to update\n const ok = await Promise.race([\n new Promise(resolve => {\n database.on(\n 'update.bans',\n (listener = () => {\n const banList = (omegga.getBanList() || { banList: {} })\n .banList;\n if (banList[id]) {\n const entry: IStoreBanHistory = {\n type: 'banHistory',\n banned: id,\n bannerId: null,\n created: parseBrickadiaTime(banList[id].created),\n expires: parseBrickadiaTime(banList[id].expires),\n reason: banList[id].reason,\n };\n\n database.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n\n resolve(true);\n }\n }),\n );\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n database.off('update.bans', listener);\n return ok;\n },\n );\n\n rpc.addMethod(\n 'player.kick',\n async ([id, reason = 'No Reason']: [id: string, reason: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n if (typeof reason !== 'string' || reason.length > 128) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n\n // ensure player exists\n const player = omegga.players.find(p => p.id === id);\n if (!player) return false;\n\n log('Kicking player', player.name.yellow);\n\n // kick the user\n omegga.writeln(`Chat.Command /Kick \"${id}\" \"${reason}\"`);\n\n // wait for the player to disconnect\n const ok = await Promise.race([\n new Promise(resolve => {\n omegga.once('leave', async (leavingPlayer: IPlayer) => {\n if (leavingPlayer.id === id) {\n resolve(true);\n\n // add kick to database\n const entry: IStoreKickHistory = {\n type: 'kickHistory',\n kicked: id,\n kickerId: null,\n created: Date.now(),\n reason,\n };\n\n await database.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n }\n });\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n\n return ok;\n },\n );\n\n rpc.addMethod('player.unban', async ([id]: [id: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n\n // check if user is banned\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n\n log(\n 'Unbanning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n\n // unban the user\n omegga.writeln(`Chat.Command /Unban \"${id}\"`);\n let listener;\n\n // wait for the ban to update\n const ok = await Promise.race([\n new Promise(resolve => {\n database.on(\n 'update.bans',\n (listener = () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (\n !banList[id] ||\n parseBrickadiaTime(banList[id].expires) < Date.now()\n )\n resolve(true);\n }),\n );\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n database.off('update.bans', listener);\n return ok;\n });\n\n rpc.addMethod('player.clearbricks', async ([id]: [id: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n\n log(\n 'Clearing bricks for player',\n omegga.getNameCache()?.savedPlayerNames?.[id]?.yellow ??\n 'with id ' + id.yellow,\n );\n\n // unban the user\n omegga.writeln(`Bricks.Clear \"${id}\"`);\n return true;\n });\n\n // set plugin config\n // TODO: add permission check\n rpc.addMethod(\n 'plugin.config',\n async ([shortPath, config]: [string, Record<string, unknown>]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return null;\n\n await plugin.storage.setConfig(config);\n // TODO: validate configs\n return true;\n },\n );\n\n // reload all plugins (and scan for new ones)\n // TODO: add permission check\n rpc.addMethod('plugins.reload', async () => {\n if (!omegga.pluginLoader) {\n error('Omegga is not using plugins');\n return false;\n }\n\n log('Unloading current plugins');\n let success = await omegga.pluginLoader.unload();\n if (!success) {\n error('Could not unload all plugins');\n return false;\n }\n\n log('Scanning for new plugins');\n success = await omegga.pluginLoader.scan();\n if (!success) {\n error('Could not scan for plugins');\n return false;\n }\n\n log('Starting plugins');\n success = await omegga.pluginLoader.reload();\n if (success) {\n const plugins = omegga.pluginLoader.plugins\n .filter(p => p.isLoaded())\n .map(p => p.getName());\n log('Loaded', (plugins.length + '').yellow, 'plugins:', plugins);\n return true;\n } else {\n error('Could not load all plugins');\n return false;\n }\n });\n\n // unload a plugin\n // TODO: add permission check\n rpc.addMethod('plugin.unload', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n if (!plugin.isLoaded()) return false;\n log('Unloading'.red, 'plugin', plugin.getName().yellow);\n return await plugin.unload();\n });\n\n // load a plugin\n // TODO: add permission check\n rpc.addMethod('plugin.load', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n if (plugin.isLoaded() || !plugin.isEnabled()) return false;\n log('Loading'.green, 'plugin', plugin.getName().yellow);\n return await plugin.load();\n });\n\n // enable/disable a plugin\n // TODO: add permission check\n rpc.addMethod(\n 'plugin.toggle',\n ([shortPath, enabled]: [string, boolean]) => {\n if (typeof enabled !== 'boolean') return;\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n try {\n plugin.setEnabled(enabled);\n log(\n enabled ? 'Enabled'.green : 'Disabled'.red,\n 'plugin',\n plugin.getName().yellow,\n );\n return true;\n } catch (e) {\n error(\n 'Error',\n enabled ? 'enabling'.green : 'disabling'.red,\n 'plugin',\n plugin.getName().yellow,\n );\n return false;\n }\n },\n );\n\n // get a list of roles\n // TODO: add permission check\n rpc.addMethod('roles.list', () => {\n return _.sortBy(omegga.getRoleSetup()?.roles ?? [], p =>\n p.name.toLowerCase(),\n );\n });\n\n // get a paginated list of users\n // TODO: add permission check\n rpc.addMethod(\n 'users.list',\n async ([{ page = 0, search = '', sort = 'name', direction = '1' } = {}]: [\n {\n page?: number;\n search?: string;\n sort?: string;\n direction?: string;\n },\n ]) => {\n const resp: GetUsersRes = await database.getUsers({\n page,\n search,\n sort,\n direction,\n });\n const now = Date.now();\n resp.users = resp.users.map(({ hash: _, ...user }) => ({\n ...user,\n hash: '',\n seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,\n createdAgo: now - user.created,\n }));\n return resp;\n },\n );\n\n // create a new user (host only at the moment)\n // TODO: add permission check\n rpc.addMethod(\n 'users.create',\n async ([username, password]: [string, string]) => {\n if (!socket.data.user.isOwner) return 'missing permission';\n\n // body is username and password\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n\n // validate username\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n\n // this validation is here for _those_ people\n if (password.length === 0 || password.length > 128)\n return 'invalid password size';\n\n // set owner's credential as the first user\n if (socket.data.user.isOwner && socket.data.user.username === '') {\n // set the owner's username and password\n try {\n await database.stores.users.update(\n { _id: socket.data.user._id },\n {\n username,\n hash: await database.hash(password),\n },\n );\n } catch (e) {\n error('error setting owner password', e);\n return 'error setting owner password';\n }\n\n log(`created account as \"${username.yellow}\"`);\n\n // update\n return '';\n }\n\n // check if user exists\n if (await database.userExists(username)) return 'user already exists';\n\n try {\n await database.createUser(username, password);\n } catch (e) {\n error('error creating new user', e);\n return 'error creating new user';\n }\n\n log(`created new user \"${username.yellow}\"`);\n\n return '';\n },\n );\n\n // change a user's password\n // TODO: add permission check\n rpc.addMethod(\n 'users.passwd',\n async ([username, password]: [string, string]) => {\n // the owner can change names and the user can change their own name\n if (!socket.data.user.isOwner && username !== socket.data.user.username)\n return 'missing permission';\n\n // body is username and password\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n\n // validate username\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n\n // check if user exists\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n try {\n await database.userPasswd(username, password);\n } catch (e) {\n error('error setting user password', e);\n return \"error setting user's password\";\n }\n\n log(`changed password for \"${username.yellow}\"`);\n\n return '';\n },\n );\n\n rpc.addMethod('server.autorestart.get', async () => {\n return await database.getAutoRestartConfig();\n });\n\n const autorestartConfigFields: Record<\n keyof IStoreAutoRestartConfig,\n string\n > = {\n type: 'string',\n maxUptime: 'number',\n maxUptimeEnabled: 'boolean',\n emptyUptime: 'number',\n emptyUptimeEnabled: 'boolean',\n dailyHour: 'number',\n dailyHourEnabled: 'boolean',\n announcementEnabled: 'boolean',\n playersEnabled: 'boolean',\n saveWorld: 'boolean',\n autoUpdateEnabled: 'boolean',\n autoUpdateIntervalMins: 'number',\n };\n\n rpc.addMethod(\n 'server.autorestart.set',\n async ([config]: [IStoreAutoRestartConfig]) => {\n // validate the autorestart config\n if (\n typeof config !== 'object' ||\n !Object.entries(autorestartConfigFields).every(\n ([k, v]) =>\n k in config && typeof config[k] === v && !Number.isNaN(v),\n ) ||\n config.type !== 'autoRestartConfig'\n )\n return false;\n await database.setAutoRestartConfig(config);\n return true;\n },\n );\n\n // send server status at request\n // TODO: server status permission check\n rpc.addMethod('server.status', () => {\n return server.lastReportedStatus;\n });\n\n // get server run status\n // TODO: server status permission check\n rpc.addMethod('server.started', () => {\n return {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n });\n\n // start the server if it's not already started\n // TODO: server status permission check\n rpc.addMethod('server.start', async () => {\n if (omegga.starting || omegga.stopping || omegga.started) return;\n log('Starting server...');\n await omegga.start();\n });\n\n // stop the server if it's not already stopped\n // TODO: server status permission check\n rpc.addMethod('server.stop', async () => {\n if (omegga.starting || omegga.stopping || !omegga.started) return;\n log('Stopping server...');\n await omegga.stop();\n });\n\n // restart the server if it's running, start the server if it's stopped\n // TODO: server status permission check\n rpc.addMethod('server.restart', async () => {\n if (omegga.starting || omegga.stopping) return;\n\n try {\n const config = await database.getAutoRestartConfig();\n await omegga.saveServer({\n players: config.playersEnabled,\n saveWorld: config.saveWorld ?? true,\n announcement: config.announcementEnabled,\n });\n } catch (err) {\n error('Error while saving server setup', err);\n }\n database.addChatLog('server', {}, 'Restarting in 5 seconds...');\n Logger.logp('Restarting in 5 seconds...');\n omegga.broadcast(\n `<size=\"20\">Server restart in <b><color=\"ffffbb\">${5} seconds</></></>`,\n );\n await new Promise(resolve => setTimeout(resolve, 5000));\n await omegga.restartServer();\n });\n\n // check if the server has a steam update available\n rpc.addMethod('server.updatecheck', async () => {\n if (!omegga.config.__STEAM) return null;\n const lastUpdate = getLastSteamUpdateCheck();\n if (Date.now() - lastUpdate.attempt < 1000 * 5) {\n Logger.verbose('[rpc] Using cached steam update check');\n return lastUpdate.result;\n }\n Logger.verbose('[rpc] Checking for steam update');\n return await hasSteamUpdate(omegga.config.server?.steambeta);\n });\n\n // check if the server has a steam update available\n rpc.addMethod('server.update', async () => {\n if (!omegga.config.__STEAM) return false;\n if (omegga.stopping || omegga.starting) return false;\n const wasStarted = omegga.started;\n if (wasStarted) {\n log('Stopping server to update...');\n await omegga.stop();\n }\n log('Updating server...');\n let ok = false;\n try {\n steamcmdDownloadGame({\n steambeta: omegga.config.server?.steambeta,\n steambetaPassword: omegga.config.server?.steambetaPassword,\n });\n ok = true;\n log('Server updated successfully');\n } catch (err) {\n error('Error updating server', err);\n }\n if (wasStarted) {\n log('Starting server...');\n await omegga.start();\n }\n\n return ok;\n });\n\n // list available worlds\n rpc.addMethod('world.list', async () => {\n const prefix = omegga.worldPath + '/';\n return omegga\n .getWorlds()\n .map(world => world.replace(prefix, '').replace(/\\.brdb$/, ''));\n });\n\n // list a world's revisions (while the server is running)\n rpc.addMethod('world.revisions', async ([world]: [string]) => {\n try {\n return (await omegga.getWorldRevisions(world))?.map(\n ({ date, ...r }) => ({ ...r, date: date.getTime() }),\n ) satisfies WorldRevisionsRes;\n } catch (_err) {\n return null;\n }\n });\n\n rpc.addMethod('world.meta', async ([world]: [string]) => {\n try {\n const path = omegga.getWorldPath(world);\n if (!path) return null;\n return readBrdbMeta(path) satisfies WorldMetaRes;\n } catch (err) {\n error('Error while getting world meta', err);\n return null;\n }\n });\n\n // get the next world to load\n rpc.addMethod('world.next', async () => {\n return omegga.getNextWorld();\n });\n\n // get the configured default world\n rpc.addMethod('world.active', async () => {\n return omegga.getActiveWorld();\n });\n\n // load a world or revision of a world\n rpc.addMethod(\n 'world.load',\n async ([world, revision]: [string, number?]) => {\n try {\n let res = false;\n if (revision) {\n log('Loading world revision', revision, 'of', world.yellow);\n res = await omegga.loadWorldRevision(world, revision);\n } else {\n log('Loading world', world.yellow);\n res = await omegga.loadWorld(world);\n }\n if (res) {\n log('World loaded successfully');\n } else {\n log('World load failed');\n }\n return res;\n } catch (err) {\n error('Error while loading world', err);\n return false;\n }\n },\n );\n\n /// set or clear the default world\n rpc.addMethod('world.use', async ([world]: [string?]) => {\n try {\n const ok = omegga.setActiveWorld(world || null);\n if (ok) {\n log('Set default world to', (world || 'none').yellow);\n }\n return ok;\n } catch (err) {\n error('Error while using world', err);\n return false;\n }\n });\n\n // create a new world\n rpc.addMethod(\n 'world.create',\n async ([name, level]: [\n string,\n ('Plate' | 'Space' | 'Studio' | 'Peaks')?,\n ]) => {\n try {\n log(\n 'Creating new world',\n name.yellow,\n 'with level',\n level?.yellow || 'Plate',\n );\n const res = await omegga.createEmptyWorld(name, level);\n if (res) {\n log('World created successfully');\n } else {\n log('World creation failed');\n }\n return res;\n } catch (err) {\n error('Error while creating world', err);\n return false;\n }\n },\n );\n\n // save a world or save as a world\n rpc.addMethod('world.save', async ([name]: [string?]) => {\n try {\n let res = false;\n if (name) {\n log('Saving world as', name.yellow);\n res = await omegga.saveWorldAs(name);\n } else {\n log('Saving current world');\n res = await omegga.saveWorld();\n }\n if (res) {\n log('World saved successfully');\n } else {\n log('World save failed');\n }\n return res;\n } catch (err) {\n error('Error while saving world', err);\n return false;\n }\n });\n\n // subscribe and unsubscribe to events\n socket.on('subscribe', room => {\n // TODO: permission check for certain rooms\n if (server.rooms.includes(room)) {\n socket.join(room);\n }\n });\n socket.on('unsubscribe', room => {\n if (server.rooms.includes(room)) socket.leave(room);\n });\n\n socket.on('disconnect', () => {});\n });\n\n // register routes\n server.app.use('/api/v1', openApi);\n server.app.use('/api/v1', api);\n}\n"],"names":["VERSION","getLastSteamUpdateCheck","Logger","JSONRPCServer","JSONRPCClient","JSONRPCServerAndClient","parseLinks","sanitize","parseBrickadiaTime","player","Player","color","rgbToHex","uuid.match","banList","_","hasSteamUpdate","steamcmdDownloadGame","readBrdbMeta"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","sources":["../../../src/webserver/backend/api.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { IPluginDocumentation } from '@/plugin';\nimport { steamcmdDownloadGame } from '@/updater';\nimport { getLastSteamUpdateCheck, hasSteamUpdate } from '@/updater/steam';\nimport { VERSION } from '@/version';\nimport Player from '@omegga/player';\nimport { parseLinks, sanitize } from '@util/chat';\nimport { rgbToHex } from '@util/color';\nimport { parseBrickadiaTime } from '@util/time';\nimport * as uuid from '@util/uuid';\nimport express from 'express';\nimport {\n JSONRPCClient,\n JSONRPCServer,\n JSONRPCServerAndClient,\n} from 'json-rpc-2.0';\nimport _ from 'lodash';\nimport Database from './database';\nimport type Webserver from './index';\nimport {\n IFrontendBanEntry,\n IPlayer,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreKickHistory,\n IStoreUser,\n IUserAgo,\n IUserHistory,\n IUserNote,\n OmeggaSocketIo,\n} from './types';\nimport { Plugin } from '@omegga/plugin/interface';\nimport { readBrdbMeta } from '@util/brdb';\n\nexport type OmeggaSocketData = {\n roles: { type: 'role'; name: string }[];\n version: string;\n brickadiaVersion: number | null;\n canLogOut: boolean;\n now: number;\n userless: boolean;\n isSteam: boolean;\n update: {\n canCheck: boolean;\n lastCheck: boolean | null;\n };\n user: {\n username: string;\n isOwner: boolean;\n roles: string[];\n };\n};\n\nexport type GetPlayersRes = {\n pages: number;\n total: number;\n players: (IUserHistory & IUserAgo & { ban?: IFrontendBanEntry })[];\n};\n\nexport type GetPlayerRes = Omit<IUserHistory, 'nameHistory'> & {\n banHistory: (IStoreBanHistory & {\n duration?: number;\n bannerName?: string;\n })[];\n kickHistory: (IStoreKickHistory & {\n kickerName?: string;\n })[];\n notes: IUserNote[];\n nameHistory: {\n name: string;\n displayName: string;\n date: number;\n ago?: number;\n }[];\n} & IUserAgo & {\n isHost: boolean;\n isOnline: boolean;\n currentBan: IFrontendBanEntry | null;\n roles: { name: string; color: string }[];\n };\n\nexport type GetPluginsRes = {\n name: string;\n documentation: IPluginDocumentation;\n path: string;\n isLoaded: boolean;\n isEnabled: boolean;\n}[];\n\nexport type GetPluginRes = {\n name: string;\n format: string;\n info: Record<string, unknown>;\n documentation: IPluginDocumentation;\n config: Record<string, unknown>;\n defaultConfig: Record<string, unknown>;\n objCount: number;\n path: string;\n isLoaded: boolean;\n isEnabled: boolean;\n};\n\nexport type GetUsersRes = {\n pages: number;\n total: number;\n users: (IStoreUser & IUserAgo)[];\n};\n\nexport type HistoryRes = Awaited<ReturnType<Database['getChats']>>;\n\nexport type WorldRevisionsRes = {\n index: number;\n date: number;\n note: string;\n}[];\n\nexport type WorldMetaRes = {\n meta: {\n world: {\n environment: string;\n };\n bundle: {\n type: 'World';\n iD: string;\n name: string;\n version: string;\n tags: string[];\n authors: string[];\n createdAt: string;\n updatedAt: string;\n description: string;\n dependencies: string[];\n };\n };\n owners: {\n id: string;\n name: string;\n display_name: string;\n entity_count: number;\n brick_count: number;\n component_count: number;\n wire_count: number;\n }[];\n};\n\nexport default function (server: Webserver, io: OmeggaSocketIo) {\n const { database, omegga } = server;\n\n // open API is accessible without auth\n const openApi = express.Router();\n const api = express.Router();\n\n // check if this is the first user in the database\n openApi.get('/first', async (req, res) =>\n res.json(await database.isFirstUser()),\n );\n\n // login / create admin user route\n openApi.post('/auth', async (req, res) => {\n // body is username and password\n if (\n typeof req.body !== 'object' ||\n typeof req.body.username !== 'string' ||\n typeof req.body.password !== 'string'\n ) {\n return res.status(422).json({ message: 'invalid body' });\n }\n const { username, password } = req.body;\n\n // username regex\n if (!username.match(/^\\w{0,32}$/)) {\n return res.status(422).json({ message: 'invalid body' });\n }\n\n // if this is the first user, create it as the admin user\n const isFirst = await database.isFirstUser();\n let user;\n if (isFirst) {\n user = await database.createAdminUser(\n username,\n username === '' ? '' : password,\n );\n } else {\n user = await database.authUser(username, password);\n }\n\n if (user) {\n req.session.userId = user._id;\n req.session.save();\n res.status(200).json({});\n } else {\n res.status(404).json({ message: 'no user found' });\n }\n });\n\n // kill a session\n api.get('/logout', (req, res) => {\n req.session.destroy(e => {\n res.status(e ? 500 : 200).json({});\n });\n });\n\n // authentication middleware for api\n api.all('*', (req, _res, next) => {\n (async () => {\n const user = await database.findUserById(req.session.userId);\n if (!user || user.isBanned) return next(new Error('unauthorized'));\n // req.user = user;\n\n next();\n })();\n });\n\n // websocket data\n io.on('connection', socket => {\n // let this user receive messages directed to this user\n socket.join('user:' + socket.data.user._id);\n\n database.getRoles().then(roles => {\n socket.emit('data', {\n roles,\n version: VERSION,\n brickadiaVersion: omegga.version ?? null,\n canLogOut: socket.data.user.username !== '',\n now: Date.now(), // this can be used for the frontend to anticipate drift\n userless: !socket.data.user.username,\n user: {\n username: socket.data.user.username || 'Admin',\n isOwner: socket.data.user.isOwner,\n roles: socket.data.user.roles,\n },\n isSteam: Boolean(omegga.config.__STEAM),\n update: {\n canCheck: omegga.config.__STEAM && !omegga.config.server?.steambeta,\n lastCheck: getLastSteamUpdateCheck()?.result ?? null,\n },\n } satisfies OmeggaSocketData);\n });\n\n // logging for this user in the console for web ui actions\n const usernameText = `[${\n (socket.data.user.username || 'Admin').brightMagenta\n }]`;\n const log = (...args: any[]) => Logger.logp(usernameText, ...args);\n const error = (...args: any[]) => Logger.errorp(usernameText, ...args);\n\n // rpc connection\n const rpcServer = new JSONRPCServer();\n const rpcClient = new JSONRPCClient(async (data: any) => {\n socket.emit('rpc', data);\n });\n const rpc = new JSONRPCServerAndClient(rpcServer, rpcClient);\n socket.on('rpc', data => {\n if (data && typeof data === 'object') {\n try {\n rpc.receiveAndSend(data);\n } catch (e) {\n // silently discard broken rpc requests\n }\n }\n });\n\n // send chat message from web ui\n // TODO: check if this user has a role with permission to chat\n rpc.addMethod('chat', async ([message]: [string]) => {\n if (typeof message !== 'string') return;\n if (message.length > 140) message = message.slice(0, 140);\n\n // create fake user\n const user = {\n name: socket.data.user.username || 'Admin',\n id: socket.data.user.playerId,\n color: 'ff00ff',\n web: true,\n };\n\n // create database entry, send to web ui\n io.to('chat').emit(\n 'chat',\n await database.addChatLog('msg', user, message),\n );\n\n // broadcast to chat\n omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">${user.name}</></>]: ${parseLinks(\n sanitize(message),\n )}\"`,\n );\n\n // broadcast to terminal\n Logger.log(`[${user.name.brightMagenta.underline}]: ${message}`);\n\n return 'ok';\n });\n\n // read recent chat messages\n // TODO: add permission check\n rpc.addMethod('chat.recent', () => {\n return database.getChats({ sameServer: true });\n });\n\n // find chat messages after a certain time\n // TODO: add permission check\n rpc.addMethod(\n 'chat.history',\n ([{ after, before }]: [{ after?: number; before?: number }] = [{}]) => {\n return database.getChats({ after, before });\n },\n );\n\n // see what days chat messages were sent\n // TODO: add permission check\n rpc.addMethod('chat.calendar', () => {\n return database.calendar.years;\n });\n\n // get the list of plugins\n // TODO: add permission check\n rpc.addMethod('plugins.list', () => {\n return _.sortBy(\n omegga.pluginLoader.plugins.map(p => ({\n name: p.getName(),\n documentation: p.getDocumentation(),\n path: p.shortPath,\n isLoaded: p.isLoaded(),\n isEnabled: p.isEnabled(),\n })),\n p => p.name.toLowerCase(),\n ) satisfies GetPluginsRes;\n });\n\n // get information on a specific plugin\n // TODO: add permission check\n rpc.addMethod('plugin.get', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return null;\n\n // get the plugin configs\n const [defaultConfig, config, objCount] = await Promise.all([\n plugin.storage.getDefaultConfig(),\n plugin.storage.getConfig(),\n plugin.storage.count(),\n ]);\n\n return {\n name: plugin.getName(),\n format: (plugin.constructor as typeof Plugin).getFormat(),\n info: plugin.getInfo(),\n documentation: plugin.getDocumentation(),\n config,\n defaultConfig,\n objCount,\n path: plugin.shortPath,\n isLoaded: plugin.isLoaded(),\n isEnabled: plugin.isEnabled(),\n } satisfies GetPluginRes;\n });\n\n // get a paginated list of players\n // TODO: add permission check\n rpc.addMethod(\n 'players.list',\n async ([\n {\n page = 0,\n search = '',\n sort = 'name',\n direction = '1',\n filter = '',\n } = {},\n ]: [\n {\n page?: number;\n search?: string;\n sort?: string;\n direction?: string;\n filter?: string;\n },\n ]) => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n\n // get the ban list\n const now = Date.now();\n\n let limitId;\n\n // if the filter is set to banned players only, limit player results by ids in the current ban list\n if (filter === 'banned') {\n limitId = Object.keys(banList).filter(\n b =>\n banList[b].expires <= banList[b].created ||\n parseBrickadiaTime(banList[b].expires) > Date.now(),\n );\n }\n\n const resp: GetPlayersRes = await database.getPlayers({\n page,\n search,\n sort,\n direction,\n limitId,\n });\n\n for (const player of resp.players) {\n player.seenAgo = now - player.lastSeen;\n player.createdAgo = now - player.created;\n\n const foundBan = banList[player.id];\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n\n // create a clone of the object\n let currentBan: IFrontendBanEntry = {\n ...foundBan,\n // parse the times in the current ban\n created,\n expires,\n duration: expires - created,\n remainingTime: expires - now,\n\n // lookup banner name\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n\n // if the ban is expired, it should not be listed\n if (currentBan.expires < now && currentBan.duration > 0)\n currentBan = null;\n player.ban = currentBan;\n }\n }\n return resp;\n },\n );\n\n // get a specific player's info\n // TODO: add permission check\n rpc.addMethod('player.get', async ([id]: [string]) => {\n const entry: Omit<IUserHistory, 'nameHistory'> & {\n banHistory: (IStoreBanHistory & {\n duration?: number;\n bannerName?: string;\n })[];\n kickHistory: (IStoreKickHistory & { kickerName?: string })[];\n notes: IUserNote[];\n nameHistory: {\n name: string;\n displayName: string;\n date: number;\n ago?: number;\n }[];\n } & IUserAgo = await database.getPlayer(id);\n if (!entry) return null;\n const now = Date.now();\n\n entry.seenAgo = now - entry.lastSeen;\n entry.createdAgo = now - entry.created;\n for (const n of entry.nameHistory) n.ago = now - n.date;\n\n // combine player roles with server roles\n const playerRoles = Player.getRoles(omegga, id) || [];\n const { roles: serverRoles = [] } = omegga.getRoleSetup() ?? {};\n\n // Get banner name and duration from list of bans\n for (const b of entry.banHistory) {\n b.duration = b.expires - b.created;\n // lookup banner name\n b.bannerName = _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.bannerId],\n '',\n );\n }\n\n // Get kicker name from list of kicks\n for (const b of entry.kickHistory) {\n // lookup banner name\n b.kickerName = _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.kickerId],\n '',\n );\n }\n\n const foundBan = (omegga.getBanList() || { banList: {} }).banList[id];\n let currentBan: IFrontendBanEntry = null;\n\n if (foundBan) {\n // parse the times in the current ban\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n // create a clone of the object\n currentBan = {\n ...foundBan,\n created,\n expires,\n duration: expires - created,\n remainingTime: expires - now,\n // lookup banner name\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n\n // if the ban is expired, it should not be listed\n if (currentBan.expires < now && currentBan.duration > 0)\n currentBan = null;\n }\n\n return {\n // database results\n ...entry,\n\n // player is host\n isHost: omegga.getHostId() === id,\n\n isOnline: omegga.players.some(p => p.id === id),\n\n // player's current ban state\n currentBan,\n\n // all player's roles w/ colors\n roles: playerRoles.map(r => {\n // default role color is white\n let color = 'ffffff';\n\n // find the role (if it exists) and get the color\n const role = serverRoles.find(\n sr => sr.name.toLowerCase() === r.toLowerCase(),\n );\n if (role && role.bHasColor) color = rgbToHex(role.color);\n\n return {\n name: r,\n color: color,\n };\n }),\n } satisfies GetPlayerRes;\n });\n\n rpc.addMethod(\n 'player.ban',\n async ([id, duration = -1, reason = 'No Reason']: [\n id: string,\n duration: number,\n reason: string,\n ]) => {\n // validate inputs\n if (typeof duration !== 'number') return false;\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n if (typeof reason !== 'string' || reason.length > 128) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n log(\n 'Banning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n // ban the user\n omegga.writeln(`Chat.Command /Ban \"${id}\" ${duration} \"${reason}\"`);\n let listener;\n\n // wait for the ban to update\n const ok = await Promise.race([\n new Promise(resolve => {\n database.on(\n 'update.bans',\n (listener = () => {\n const banList = (omegga.getBanList() || { banList: {} })\n .banList;\n if (banList[id]) {\n const entry: IStoreBanHistory = {\n type: 'banHistory',\n banned: id,\n bannerId: null,\n created: parseBrickadiaTime(banList[id].created),\n expires: parseBrickadiaTime(banList[id].expires),\n reason: banList[id].reason,\n };\n\n database.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n\n resolve(true);\n }\n }),\n );\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n database.off('update.bans', listener);\n return ok;\n },\n );\n\n rpc.addMethod(\n 'player.kick',\n async ([id, reason = 'No Reason']: [id: string, reason: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n if (typeof reason !== 'string' || reason.length > 128) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n\n // ensure player exists\n const player = omegga.players.find(p => p.id === id);\n if (!player) return false;\n\n log('Kicking player', player.name.yellow);\n\n // kick the user\n omegga.writeln(`Chat.Command /Kick \"${id}\" \"${reason}\"`);\n\n // wait for the player to disconnect\n const ok = await Promise.race([\n new Promise(resolve => {\n omegga.once('leave', async (leavingPlayer: IPlayer) => {\n if (leavingPlayer.id === id) {\n resolve(true);\n\n // add kick to database\n const entry: IStoreKickHistory = {\n type: 'kickHistory',\n kicked: id,\n kickerId: null,\n created: Date.now(),\n reason,\n };\n\n await database.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n }\n });\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n\n return ok;\n },\n );\n\n rpc.addMethod('player.unban', async ([id]: [id: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n\n // check if user is banned\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n\n log(\n 'Unbanning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n\n // unban the user\n omegga.writeln(`Chat.Command /Unban \"${id}\"`);\n let listener;\n\n // wait for the ban to update\n const ok = await Promise.race([\n new Promise(resolve => {\n database.on(\n 'update.bans',\n (listener = () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (\n !banList[id] ||\n parseBrickadiaTime(banList[id].expires) < Date.now()\n )\n resolve(true);\n }),\n );\n }),\n new Promise(resolve => setTimeout(() => resolve(false), 5000)),\n ]);\n database.off('update.bans', listener);\n return ok;\n });\n\n rpc.addMethod('player.clearbricks', async ([id]: [id: string]) => {\n // validate inputs\n if (typeof id !== 'string' || !uuid.match(id)) return false;\n\n log(\n 'Clearing bricks for player',\n omegga.getNameCache()?.savedPlayerNames?.[id]?.yellow ??\n 'with id ' + id.yellow,\n );\n\n // unban the user\n omegga.writeln(`Bricks.Clear \"${id}\"`);\n return true;\n });\n\n // set plugin config\n // TODO: add permission check\n rpc.addMethod(\n 'plugin.config',\n async ([shortPath, config]: [string, Record<string, unknown>]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return null;\n\n await plugin.storage.setConfig(config);\n // TODO: validate configs\n return true;\n },\n );\n\n // reload all plugins (and scan for new ones)\n // TODO: add permission check\n rpc.addMethod('plugins.reload', async () => {\n if (!omegga.pluginLoader) {\n error('Omegga is not using plugins');\n return false;\n }\n\n log('Unloading current plugins');\n let success = await omegga.pluginLoader.unload();\n if (!success) {\n error('Could not unload all plugins');\n return false;\n }\n\n log('Scanning for new plugins');\n success = await omegga.pluginLoader.scan();\n if (!success) {\n error('Could not scan for plugins');\n return false;\n }\n\n log('Starting plugins');\n success = await omegga.pluginLoader.reload();\n if (success) {\n const plugins = omegga.pluginLoader.plugins\n .filter(p => p.isLoaded())\n .map(p => p.getName());\n log('Loaded', (plugins.length + '').yellow, 'plugins:', plugins);\n return true;\n } else {\n error('Could not load all plugins');\n return false;\n }\n });\n\n // unload a plugin\n // TODO: add permission check\n rpc.addMethod('plugin.unload', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n if (!plugin.isLoaded()) return false;\n log('Unloading'.red, 'plugin', plugin.getName().yellow);\n return await plugin.unload();\n });\n\n // load a plugin\n // TODO: add permission check\n rpc.addMethod('plugin.load', async ([shortPath]: [string]) => {\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n if (plugin.isLoaded() || !plugin.isEnabled()) return false;\n log('Loading'.green, 'plugin', plugin.getName().yellow);\n return await plugin.load();\n });\n\n // enable/disable a plugin\n // TODO: add permission check\n rpc.addMethod(\n 'plugin.toggle',\n ([shortPath, enabled]: [string, boolean]) => {\n if (typeof enabled !== 'boolean') return;\n const plugin = omegga.pluginLoader.plugins.find(\n p => p.shortPath === shortPath,\n );\n if (!plugin) return false;\n try {\n plugin.setEnabled(enabled);\n log(\n enabled ? 'Enabled'.green : 'Disabled'.red,\n 'plugin',\n plugin.getName().yellow,\n );\n return true;\n } catch (e) {\n error(\n 'Error',\n enabled ? 'enabling'.green : 'disabling'.red,\n 'plugin',\n plugin.getName().yellow,\n );\n return false;\n }\n },\n );\n\n // get a list of roles\n // TODO: add permission check\n rpc.addMethod('roles.list', () => {\n return _.sortBy(omegga.getRoleSetup()?.roles ?? [], p =>\n p.name.toLowerCase(),\n );\n });\n\n // get a paginated list of users\n // TODO: add permission check\n rpc.addMethod(\n 'users.list',\n async ([{ page = 0, search = '', sort = 'name', direction = '1' } = {}]: [\n {\n page?: number;\n search?: string;\n sort?: string;\n direction?: string;\n },\n ]) => {\n const resp: GetUsersRes = await database.getUsers({\n page,\n search,\n sort,\n direction,\n });\n const now = Date.now();\n resp.users = resp.users.map(({ hash: _, ...user }) => ({\n ...user,\n hash: '',\n seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,\n createdAgo: now - user.created,\n }));\n return resp;\n },\n );\n\n // create a new user (host only at the moment)\n // TODO: add permission check\n rpc.addMethod(\n 'users.create',\n async ([username, password]: [string, string]) => {\n if (!socket.data.user.isOwner) return 'missing permission';\n\n // body is username and password\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n\n // validate username\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n\n // this validation is here for _those_ people\n if (password.length === 0 || password.length > 128)\n return 'invalid password size';\n\n // set owner's credential as the first user\n if (socket.data.user.isOwner && socket.data.user.username === '') {\n // set the owner's username and password\n try {\n await database.stores.users.update(\n { _id: socket.data.user._id },\n {\n username,\n hash: await database.hash(password),\n },\n );\n } catch (e) {\n error('error setting owner password', e);\n return 'error setting owner password';\n }\n\n log(`created account as \"${username.yellow}\"`);\n\n // update\n return '';\n }\n\n // check if user exists\n if (await database.userExists(username)) return 'user already exists';\n\n try {\n await database.createUser(username, password);\n } catch (e) {\n error('error creating new user', e);\n return 'error creating new user';\n }\n\n log(`created new user \"${username.yellow}\"`);\n\n return '';\n },\n );\n\n // change a user's password\n // TODO: add permission check\n rpc.addMethod(\n 'users.passwd',\n async ([username, password]: [string, string]) => {\n // the owner can change names and the user can change their own name\n if (!socket.data.user.isOwner && username !== socket.data.user.username)\n return 'missing permission';\n\n // body is username and password\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n\n // validate username\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n\n // check if user exists\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n try {\n await database.userPasswd(username, password);\n } catch (e) {\n error('error setting user password', e);\n return \"error setting user's password\";\n }\n\n log(`changed password for \"${username.yellow}\"`);\n\n return '';\n },\n );\n\n rpc.addMethod('server.autorestart.get', async () => {\n return await database.getAutoRestartConfig();\n });\n\n const autorestartConfigFields: Record<\n keyof IStoreAutoRestartConfig,\n string\n > = {\n type: 'string',\n maxUptime: 'number',\n maxUptimeEnabled: 'boolean',\n emptyUptime: 'number',\n emptyUptimeEnabled: 'boolean',\n dailyHour: 'number',\n dailyHourEnabled: 'boolean',\n announcementEnabled: 'boolean',\n playersEnabled: 'boolean',\n saveWorld: 'boolean',\n autoUpdateEnabled: 'boolean',\n autoUpdateIntervalMins: 'number',\n crashRestartEnabled: 'boolean',\n };\n\n rpc.addMethod(\n 'server.autorestart.set',\n async ([config]: [IStoreAutoRestartConfig]) => {\n // validate the autorestart config\n if (\n typeof config !== 'object' ||\n !Object.entries(autorestartConfigFields).every(\n ([k, v]) =>\n k in config && typeof config[k] === v && !Number.isNaN(v),\n ) ||\n config.type !== 'autoRestartConfig'\n )\n return false;\n await database.setAutoRestartConfig(config);\n return true;\n },\n );\n\n // send server status at request\n // TODO: server status permission check\n rpc.addMethod('server.status', () => {\n return server.lastReportedStatus;\n });\n\n // get server run status\n // TODO: server status permission check\n rpc.addMethod('server.started', () => {\n return {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n });\n\n // start the server if it's not already started\n // TODO: server status permission check\n rpc.addMethod('server.start', async () => {\n if (omegga.starting || omegga.stopping || omegga.started) return;\n log('Starting server...');\n await omegga.start();\n });\n\n // stop the server if it's not already stopped\n // TODO: server status permission check\n rpc.addMethod('server.stop', async () => {\n if (omegga.starting || omegga.stopping || !omegga.started) return;\n log('Stopping server...');\n await omegga.stop();\n });\n\n // restart the server if it's running, start the server if it's stopped\n // TODO: server status permission check\n rpc.addMethod('server.restart', async () => {\n if (omegga.starting || omegga.stopping) return;\n\n try {\n const config = await database.getAutoRestartConfig();\n await omegga.saveServer({\n players: config.playersEnabled,\n saveWorld: config.saveWorld ?? true,\n announcement: config.announcementEnabled,\n });\n } catch (err) {\n error('Error while saving server setup', err);\n }\n database.addChatLog('server', {}, 'Restarting in 5 seconds...');\n Logger.logp('Restarting in 5 seconds...');\n omegga.broadcast(\n `<size=\"20\">Server restart in <b><color=\"ffffbb\">${5} seconds</></></>`,\n );\n await new Promise(resolve => setTimeout(resolve, 5000));\n await omegga.restartServer();\n });\n\n // check if the server has a steam update available\n rpc.addMethod('server.updatecheck', async () => {\n if (!omegga.config.__STEAM) return null;\n const lastUpdate = getLastSteamUpdateCheck();\n if (Date.now() - lastUpdate.attempt < 1000 * 5) {\n Logger.verbose('[rpc] Using cached steam update check');\n return lastUpdate.result;\n }\n Logger.verbose('[rpc] Checking for steam update');\n return await hasSteamUpdate(omegga.config.server?.steambeta);\n });\n\n // check if the server has a steam update available\n rpc.addMethod('server.update', async () => {\n if (!omegga.config.__STEAM) return false;\n if (omegga.stopping || omegga.starting) return false;\n const wasStarted = omegga.started;\n if (wasStarted) {\n log('Stopping server to update...');\n await omegga.stop();\n }\n log('Updating server...');\n let ok = false;\n try {\n steamcmdDownloadGame({\n steambeta: omegga.config.server?.steambeta,\n steambetaPassword: omegga.config.server?.steambetaPassword,\n });\n ok = true;\n log('Server updated successfully');\n } catch (err) {\n error('Error updating server', err);\n }\n if (wasStarted) {\n log('Starting server...');\n await omegga.start();\n }\n\n return ok;\n });\n\n // list available worlds\n rpc.addMethod('world.list', async () => {\n const prefix = omegga.worldPath + '/';\n return omegga\n .getWorlds()\n .map(world => world.replace(prefix, '').replace(/\\.brdb$/, ''));\n });\n\n // list a world's revisions (while the server is running)\n rpc.addMethod('world.revisions', async ([world]: [string]) => {\n try {\n return (await omegga.getWorldRevisions(world))?.map(\n ({ date, ...r }) => ({ ...r, date: date.getTime() }),\n ) satisfies WorldRevisionsRes;\n } catch (_err) {\n return null;\n }\n });\n\n rpc.addMethod('world.meta', async ([world]: [string]) => {\n try {\n const path = omegga.getWorldPath(world);\n if (!path) return null;\n return readBrdbMeta(path) satisfies WorldMetaRes;\n } catch (err) {\n error('Error while getting world meta', err);\n return null;\n }\n });\n\n // get the next world to load\n rpc.addMethod('world.next', async () => {\n return omegga.getNextWorld();\n });\n\n // get the configured default world\n rpc.addMethod('world.active', async () => {\n return omegga.getActiveWorld();\n });\n\n // load a world or revision of a world\n rpc.addMethod(\n 'world.load',\n async ([world, revision]: [string, number?]) => {\n try {\n let res = false;\n if (revision) {\n log('Loading world revision', revision, 'of', world.yellow);\n res = await omegga.loadWorldRevision(world, revision);\n } else {\n log('Loading world', world.yellow);\n res = await omegga.loadWorld(world);\n }\n if (res) {\n log('World loaded successfully');\n } else {\n log('World load failed');\n }\n return res;\n } catch (err) {\n error('Error while loading world', err);\n return false;\n }\n },\n );\n\n /// set or clear the default world\n rpc.addMethod('world.use', async ([world]: [string?]) => {\n try {\n const ok = omegga.setActiveWorld(world || null);\n if (ok) {\n log('Set default world to', (world || 'none').yellow);\n }\n return ok;\n } catch (err) {\n error('Error while using world', err);\n return false;\n }\n });\n\n // create a new world\n rpc.addMethod(\n 'world.create',\n async ([name, level]: [\n string,\n ('Plate' | 'Space' | 'Studio' | 'Peaks')?,\n ]) => {\n try {\n log(\n 'Creating new world',\n name.yellow,\n 'with level',\n level?.yellow || 'Plate',\n );\n const res = await omegga.createEmptyWorld(name, level);\n if (res) {\n log('World created successfully');\n } else {\n log('World creation failed');\n }\n return res;\n } catch (err) {\n error('Error while creating world', err);\n return false;\n }\n },\n );\n\n // save a world or save as a world\n rpc.addMethod('world.save', async ([name]: [string?]) => {\n try {\n let res = false;\n if (name) {\n log('Saving world as', name.yellow);\n res = await omegga.saveWorldAs(name);\n } else {\n log('Saving current world');\n res = await omegga.saveWorld();\n }\n if (res) {\n log('World saved successfully');\n } else {\n log('World save failed');\n }\n return res;\n } catch (err) {\n error('Error while saving world', err);\n return false;\n }\n });\n\n // subscribe and unsubscribe to events\n socket.on('subscribe', room => {\n // TODO: permission check for certain rooms\n if (server.rooms.includes(room)) {\n socket.join(room);\n }\n });\n socket.on('unsubscribe', room => {\n if (server.rooms.includes(room)) socket.leave(room);\n });\n\n socket.on('disconnect', () => {});\n });\n\n // register routes\n server.app.use('/api/v1', openApi);\n server.app.use('/api/v1', api);\n}\n"],"names":["VERSION","getLastSteamUpdateCheck","Logger","JSONRPCServer","JSONRPCClient","JSONRPCServerAndClient","parseLinks","sanitize","parseBrickadiaTime","player","Player","color","rgbToHex","uuid.match","banList","_","hasSteamUpdate","steamcmdDownloadGame","readBrdbMeta"],"mappings":"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"}
|
|
@@ -513,12 +513,14 @@ class Database extends EventEmitter {
|
|
|
513
513
|
autoUpdateIntervalMins: 60,
|
|
514
514
|
announcementEnabled: true,
|
|
515
515
|
playersEnabled: true,
|
|
516
|
-
saveWorld: true
|
|
516
|
+
saveWorld: true,
|
|
517
|
+
crashRestartEnabled: true
|
|
517
518
|
});
|
|
518
519
|
}
|
|
519
520
|
config.autoUpdateEnabled ??= true;
|
|
520
521
|
config.autoUpdateIntervalMins ??= 60;
|
|
521
522
|
config.saveWorld ??= true;
|
|
523
|
+
config.crashRestartEnabled ??= true;
|
|
522
524
|
return config;
|
|
523
525
|
}
|
|
524
526
|
async setAutoRestartConfig(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sources":["../../../src/webserver/backend/database.ts"],"sourcesContent":["import soft from '@/softconfig';\nimport { IServerConfig } from '@config/types';\nimport type Omegga from '@omegga/server';\nimport { explode } from '@util/pattern';\nimport { parseBrickadiaTime } from '@util/time';\nimport bcrypt from 'bcryptjs';\nimport chokidar from 'chokidar';\nimport EventEmitter from 'events';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport Calendar from './calendar';\nimport {\n IPlayer,\n IPunchcard,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreChat,\n IStoreKickHistory,\n IStoreServerInstance,\n IStoreUser,\n IStoreVersion,\n IUserHistory,\n IUserNote,\n} from './types';\n\n// TODO: online users graph\n// TODO: player online times\n// TODO: bricks over time graph\n// TODO: minute server status for metrics\n// TODO: chat messages per min/hour/day check\n\nlet serverInstance: IStoreServerInstance & { _id: string };\n\ntype FixedSizeArray<N extends number, T, M extends string = '0'> = {\n readonly [k in M]: any;\n} & { length: N } & ReadonlyArray<T>;\n\n// generate a punchcard (days x week)\nconst createPunchcard = (): number[][] =>\n Array.from({ length: 7 }).map(() =>\n Array.from<number>({ length: 24 }).fill(0),\n );\n\n// the database keeps track of metrics for omegga\nexport default class Database extends EventEmitter {\n options: IServerConfig;\n omegga: Omegga;\n stores: {\n users: Datastore;\n chat: Datastore;\n players: Datastore;\n status: Datastore;\n server: Datastore;\n };\n calendar: Calendar;\n\n constructor(options: IServerConfig, omegga: Omegga) {\n super();\n this.options = options;\n this.omegga = omegga;\n this.calendar = new Calendar();\n\n // database\n const dbOpts = {\n autoload: true,\n // case insensitive string comparison\n compareStrings: (a: string, b: string) =>\n a.localeCompare(b, 'en', { ignorePunctuation: true }),\n };\n\n // create all the stores\n this.stores = {\n users: Datastore.create({\n filename: path.join(omegga.dataPath, soft.USER_STORE),\n ...dbOpts,\n }),\n chat: Datastore.create({\n filename: path.join(omegga.dataPath, soft.CHAT_STORE),\n ...dbOpts,\n }),\n players: Datastore.create({\n filename: path.join(omegga.dataPath, soft.PLAYER_STORE),\n ...dbOpts,\n }),\n status: Datastore.create({\n filename: path.join(omegga.dataPath, soft.STATUS_STORE),\n ...dbOpts,\n }),\n server: Datastore.create({\n filename: path.join(omegga.dataPath, soft.SERVER_STORE),\n ...dbOpts,\n }),\n };\n }\n\n // make sure all the databases have valid store versions\n async doMigrations() {\n // current store versions\n const storeVersions: { [key: keyof typeof this.stores]: number } = {\n users: 1,\n chat: 1,\n players: 1,\n status: 1,\n server: 1,\n\n // example version\n dummy: 10,\n };\n\n // migrations to/from certain store versions\n const migrations: {\n [key: keyof typeof this.stores]: {\n version: number;\n upgrade: (store: Datastore) => Promise<void>;\n }[];\n } = {\n users: [],\n chat: [],\n players: [],\n status: [],\n server: [],\n\n // example migration list (all version upgrades go up by 1)\n dummy: [\n {\n version: 1, // target version\n upgrade: async _store => {}, // upgrade function (does not include updating version)\n },\n ],\n };\n\n // loop through all stores to find if any need version upgrades\n await Promise.all(\n Object.entries(this.stores).map(async ([name, store]) => {\n const versionEntry = await store.findOne<IStoreVersion>({\n type: 'storeVersion',\n });\n const expected = storeVersions[name];\n // if the version was not found, insert it\n if (!versionEntry) {\n store.insert({ type: 'storeVersion', version: expected });\n\n // if the version was not what was expected\n } else if (expected !== versionEntry.version) {\n // while the version is below the expected version\n let { version } = versionEntry;\n for (; version < expected; version++) {\n // find an available migration\n const migration = migrations[name].find(v => v.version === version);\n\n // no migration found, ignore\n if (!migration) break;\n\n // run the upgrade\n migration.upgrade(store);\n }\n\n // update the stored version\n await store.update({ _id: versionEntry._id }, { version });\n }\n }),\n );\n }\n\n // get the running instance id of this omegga\n async getInstanceId() {\n if (serverInstance) return serverInstance._id;\n\n const doc = await this.stores.server.insert<IStoreServerInstance>({\n type: 'app:start',\n date: Date.now(),\n });\n\n serverInstance = doc;\n\n // create a calendar of all previous chat messages\n const dates = (\n await this.stores.chat.find<IStoreChat>({ type: 'chat' })\n ).map(c => c.created);\n for (const d of dates) {\n this.calendar.addDate(d);\n }\n\n const watcher = chokidar.watch(\n path.join(this.omegga.configPath, 'BanList.json'),\n { persistent: false },\n );\n watcher\n .on('add', () => setTimeout(this.syncBanList.bind(this), 500))\n .on('change', () =>\n setTimeout(() => {\n this.syncBanList();\n this.emit('update.bans');\n }, 500),\n );\n setTimeout(this.syncBanList.bind(this), 500);\n\n const handleKick = (kicked: IPlayer, kicker: IPlayer, reason: string) => {\n // time the event listener should expire\n const eventExpire = Date.now() + 100;\n\n // detect a single leave\n this.omegga.once('leave', async leavingPlayer => {\n const now = Date.now();\n // if a player leaves more than 100ms from the kick command, it's bugged\n if (now > eventExpire) return;\n\n if (leavingPlayer.id === kicked.id) {\n const entry = {\n type: 'kickHistory',\n kicked: kicked.id,\n kickerId: kicker.id,\n created: now,\n reason,\n };\n\n // depending on implementation, this could potentially be a kick event\n await this.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n }\n });\n };\n\n // for a5 is based on the chat event\n this.omegga.on('kick', (kicked, kicker, reason) => {\n const kickedPlayer = this.omegga.findPlayerByName(kicked);\n const kickerPlayer = this.omegga.findPlayerByName(kicker);\n\n // couldn't find kicked or kicker player\n if (!kickedPlayer || !kickerPlayer) return;\n\n handleKick(kickedPlayer, kickerPlayer, reason);\n });\n\n return doc._id;\n }\n\n // synchronize the ban list with the one in the database\n syncBanList() {\n const banList = this.omegga.getBanList()?.banList;\n if (!banList) return;\n\n // upsert all bans\n for (const banned in banList) {\n const entry: IStoreBanHistory = {\n type: 'banHistory',\n banned,\n bannerId: banList[banned].bannerId,\n created: parseBrickadiaTime(banList[banned].created),\n expires: parseBrickadiaTime(banList[banned].expires),\n reason: banList[banned].reason,\n };\n\n this.stores.players.update(entry, { $set: entry }, { upsert: true });\n }\n }\n\n // determine if this user would be the first user (admin user)\n async isFirstUser() {\n return (await this.stores.users.count({ type: 'user' })) === 0;\n }\n\n // hash a password\n async hash(password: string) {\n return await bcrypt.hash(password, 10);\n }\n\n // create an admin user account, username can be blank (no password)\n async createAdminUser(username: string, password: string) {\n const hash = await this.hash(password);\n\n // create an owner user\n const user = this.stores.users.insert({\n // this is a user\n type: 'user',\n created: Date.now(),\n lastOnline: Date.now(),\n\n // credentials\n username,\n hash,\n\n // permissions\n isOwner: true,\n roles: [],\n\n // brickadia player uuid\n playerId: '',\n });\n return user;\n }\n\n // determine if a user with this name exists\n async userExists(username: string) {\n return (await this.stores.users.count({ type: 'user', username })) > 0;\n }\n\n // create a regular user account\n async createUser(username: string, password: string) {\n if (await this.userExists(username)) throw new Error('user already exists');\n\n const hash = await this.hash(password);\n // create an owner user\n const user = this.stores.users.insert<IStoreUser>({\n // this is a user\n type: 'user',\n created: Date.now(),\n lastOnline: 0,\n\n // credentials\n username,\n hash,\n\n // permissions\n isOwner: false,\n roles: [],\n\n // brickadia player uuid\n playerId: '',\n });\n return user;\n }\n\n // set a user's password\n async userPasswd(username: string, password: string) {\n const hash = await this.hash(password);\n\n if (!this.userExists(username)) throw new Error('user does not exist');\n\n await this.stores.users.update(\n { type: 'user', username },\n { $set: { hash } },\n );\n }\n\n // get a user from credentials\n async authUser(username: string, password: string) {\n const user = await this.stores.users.findOne<IStoreUser>({ username });\n // user not found\n if (!user || user.isBanned) return null;\n\n // make sure the user's password hash is valid\n if (await bcrypt.compare(password, user.hash)) {\n // update last online status\n await this.stores.users.update(\n { _id: user._id },\n { $set: { lastOnline: Date.now() } },\n );\n return user;\n }\n\n // wrong password\n return null;\n }\n\n // find a user by object id\n async findUserById(id: string) {\n return await this.stores.users.findOne<IStoreUser>({\n $or: [\n // the owner has no username, so everyone is the owner\n { type: 'user', username: '', isOwner: true },\n\n // the user exists and has an id\n { type: 'user', _id: id },\n ],\n });\n }\n\n // get a list of roles\n // TODO: actually implement roles\n getRoles(): Promise<{ type: 'role'; name: 'string' }[]> {\n return this.stores.server.find({ type: 'role' });\n }\n\n // add a chat message to the chat log store\n async addChatLog(\n action: 'msg' | 'server' | 'leave' | 'join',\n user: IPlayer,\n message?: string,\n ) {\n this.calendar.addDate(Date.now());\n return await this.stores.chat.insert<IStoreChat>({\n type: 'chat',\n created: Date.now(),\n instanceId: await this.getInstanceId(),\n action,\n user,\n ...(message ? { message } : {}),\n });\n }\n\n // get recent chat activity\n async getChats({\n count = 50,\n sameServer,\n before,\n after,\n }: {\n count?: number;\n sameServer?: boolean;\n before?: number;\n after?: number;\n } = {}) {\n return await this.stores.chat\n .find<IStoreChat>({\n type: 'chat',\n ...(sameServer ? { instanceId: await this.getInstanceId() } : {}),\n created: before\n ? { $lt: before }\n : after\n ? { $gt: after }\n : { $lt: Date.now() },\n })\n .sort({ created: !before && after ? 1 : -1 })\n .limit(count)\n .exec();\n }\n\n // get paginated players\n async getPlayers({\n count = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = '1',\n limitId = undefined,\n }: {\n count?: number;\n search?: string;\n page?: number;\n sort?: string;\n direction?: string;\n limitId?: string[];\n } = {}) {\n const pattern = explode(search);\n\n // the query used for finding which players are available\n const query = {\n type: 'userHistory',\n // only filter if there's a query\n $and: [\n // if a limitId is passed in, the id must be in it\n ...(limitId ? [{ id: { $in: limitId } }] : []),\n\n // if there's a search, the search matches...\n ...(search.length > 0\n ? [\n {\n $or: [\n // id was pasted in\n { id: search },\n // user's current name\n { name: { $regex: pattern } },\n { displayName: { $regex: pattern } },\n // user's past name\n {\n nameHistory: { $elemMatch: { name: { $regex: pattern } } },\n },\n {\n nameHistory: {\n $elemMatch: { displayName: { $regex: pattern } },\n },\n },\n ],\n },\n ]\n : []),\n ],\n };\n\n // the query used for finding a specific player\n const exactQuery = {\n type: 'userHistory',\n // only filter if there's a query\n $or: [\n // id was pasted in\n { id: search },\n // user's current name\n { name: search },\n // user's current display name\n { displayName: search },\n ],\n };\n\n // count players and query players\n const [total, players, exactResult] = await Promise.all([\n this.stores.players.count(query),\n this.stores.players\n .find<IUserHistory>(query)\n // TODO: add other sorts and sort directions\n .sort({ [sort]: direction })\n .skip(count * page)\n .limit(count)\n .exec(),\n\n // exact match detection\n search.length > 0 && page === 0\n ? this.stores.players.find(exactQuery)\n : [],\n ]);\n\n // add exact matches to the beginning\n if (exactResult.length > 0) {\n // remove duplicates\n for (const res of exactResult) {\n const index = players.findIndex(p => p._id === res._id);\n players.splice(index, 1);\n }\n\n // add results to the beginning\n players.splice(0, 0, ...exactResult);\n }\n\n return {\n pages: Math.ceil(total / count),\n total,\n players,\n };\n }\n\n // get paginated users\n async getUsers({\n count = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = '1',\n } = {}) {\n const pattern = explode(search);\n\n // the query used for finding which players are available\n const query = {\n type: 'user',\n // only filter if there's a query\n ...(search.length > 0\n ? {\n $or: [\n // id was pasted in\n { playerId: search },\n // user's current name\n { name: { $regex: pattern } },\n ],\n }\n : {}),\n };\n\n // count players and query players\n const [total, users] = await Promise.all([\n this.stores.users.count(query),\n this.stores.users\n .find<IStoreUser>(query)\n // TODO: add other sorts and sort directions\n .sort({ [sort]: direction })\n .skip(count * page)\n .limit(count)\n .exec(),\n ]);\n\n return {\n pages: Math.ceil(total / count),\n total,\n users,\n };\n }\n\n // get an individual player, ban and kick history, and notes\n async getPlayer(id: string) {\n const [player, banHistory, kickHistory, notes] = await Promise.all([\n this.stores.players.findOne<IUserHistory>({ type: 'userHistory', id }),\n this.stores.players\n .find<IStoreBanHistory>({ type: 'banHistory', banned: id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n this.stores.players\n .find<IStoreKickHistory>({ type: 'kickHistory', kicked: id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n this.stores.players\n .find<IUserNote>({ type: 'note', id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n ]);\n\n if (!player) return null;\n\n return { ...player, banHistory, kickHistory, notes };\n }\n\n // add a user to the visit history, returns true if this is a first visit\n async addVisit(user: IPlayer) {\n const existing = await this.stores.players.findOne<IUserHistory>({\n type: 'userHistory',\n id: user.id,\n });\n const now = Date.now();\n const instanceId = await this.getInstanceId();\n if (!existing) {\n // create the player\n // TODO: add some form of admin note tool for moderators to leave notes\n // on troublesome players + warnings\n await this.stores.players.insert<IUserHistory>({\n type: 'userHistory',\n // base brickadia user info\n id: user.id,\n name: user.name,\n displayName: user.displayName,\n // list of names and when they were first used\n nameHistory: [\n { name: user.name, displayName: user.displayName, date: now },\n ],\n // list of ips this player has been on\n ips: [],\n // first time player was seen\n created: now,\n // last time player was seen\n lastSeen: now,\n // last server this player has been on\n lastInstanceId: instanceId,\n // number of status checks this player has observed\n heartbeats: 0,\n // number of sessions (at least an hour apart) this player has had\n sessions: 1,\n // number of instances (times omegga was started) this player has joined\n instances: 1,\n });\n return true;\n } else {\n await this.stores.players.update<IUserHistory>(\n { _id: existing._id },\n {\n $inc: {\n // increment number of sessions if the player hasn't been seen for an hour\n sessions: existing.lastSeen < now - 60 * 60 * 1000 ? 1 : 0,\n // increment the number of instances this player has joined\n instances: existing.lastInstanceId === instanceId ? 1 : 0,\n },\n\n $set: {\n // update last seen time and user name\n lastSeen: now,\n name: user.name,\n displayName: user.displayName,\n\n // set the last joined instance\n lastInstanceId: instanceId,\n },\n\n // add the name to the history if it was not already in it\n ...(existing.nameHistory.some(\n h => h.name === user.name && h.displayName === user.displayName,\n )\n ? {}\n : {\n $addToSet: {\n nameHistory: {\n name: user.name,\n displayName: user.displayName,\n date: now,\n },\n },\n }),\n },\n );\n return false;\n }\n }\n\n // use data from minutely heartbeats to fuel metrics\n async addHeartbeat(data: {\n bricks: number;\n players: string[];\n ips: Record<string, string>;\n }) {\n const now = Date.now();\n\n // add the heartbeat to the server status store\n await this.stores.status.insert({\n type: 'heartbeat',\n created: now,\n bricks: data.bricks,\n players: data.players,\n });\n\n // update player heartbeats and last seens\n await this.stores.players.update(\n {\n // all players in the status update\n id: { $in: data.players },\n },\n {\n // update last seen\n $set: { lastSeen: now },\n // increment heartbeats\n $inc: { heartbeats: 1 },\n },\n { multi: true },\n );\n\n // get all players in the status update\n const players = await this.stores.players.find<IUserHistory>({\n id: { $in: data.players },\n });\n\n await Promise.all(\n players\n // find players without the discovered ip\n .filter(p => !(p.ips || []).includes(data.ips[p.id]))\n // insert the IP into the player's ip list\n .map(p =>\n this.stores.players.update(\n { _id: p._id },\n {\n $addToSet: { ips: data.ips[p.id] },\n },\n ),\n ),\n );\n }\n\n async getAutoRestartConfig(): Promise<IStoreAutoRestartConfig> {\n let config = await this.stores.server.findOne<IStoreAutoRestartConfig>({\n type: 'autoRestartConfig',\n });\n if (!config) {\n config = await this.stores.server.insert({\n type: 'autoRestartConfig',\n maxUptime: 48,\n maxUptimeEnabled: false,\n emptyUptime: 24,\n emptyUptimeEnabled: false,\n dailyHour: 2,\n dailyHourEnabled: false,\n autoUpdateEnabled: true,\n autoUpdateIntervalMins: 60,\n announcementEnabled: true,\n playersEnabled: true,\n saveWorld: true,\n });\n }\n\n config.autoUpdateEnabled ??= true;\n config.autoUpdateIntervalMins ??= 60;\n config.saveWorld ??= true;\n\n return config;\n }\n\n async setAutoRestartConfig(config: IStoreAutoRestartConfig): Promise<void> {\n config.maxUptime = Math.round(Math.max(1, Math.min(config.maxUptime, 168)));\n config.emptyUptime = Math.round(\n Math.max(1, Math.min(config.emptyUptime, 168)),\n );\n config.dailyHour = Math.round(Math.max(0, Math.min(config.dailyHour, 23)));\n config.autoUpdateIntervalMins = Math.round(\n Math.max(10, Math.min(config.autoUpdateIntervalMins ?? 60, Infinity)),\n );\n config.autoUpdateEnabled ??= true;\n await this.stores.server.update(\n { type: 'autoRestartConfig' },\n {\n $set: {\n ...config,\n },\n },\n { upsert: false },\n );\n }\n\n // update player online-time punchcard\n async updatePlayerPunchcard(numNewPlayers: number) {\n // get date, utc hour, and utc day\n const now = new Date();\n const time = now.getTime();\n const hour = now.getUTCHours();\n const day = now.getUTCDay();\n const month = now.getUTCMonth();\n const year = now.getUTCFullYear();\n\n // find the punchcard\n const card = await this.stores.status.findOne<IPunchcard>({\n type: 'punchcard',\n kind: 'playerCount',\n month,\n year,\n });\n\n // create a new punchcard if one does not exist for this month\n if (!card) {\n const punchcard = createPunchcard();\n punchcard[day][hour] = numNewPlayers;\n // insert it\n await this.stores.status.insert<IPunchcard>({\n type: 'punchcard',\n kind: 'playerCount',\n created: time,\n updated: time,\n month,\n year,\n punchcard,\n });\n\n // if the card exists, check if it was already updated this hour\n } else {\n // add the players to this slot\n card.punchcard[day][hour] += numNewPlayers;\n // update the punchcard\n await this.stores.status.update(\n { _id: card._id },\n {\n $set: {\n punchcard: card.punchcard,\n updated: time,\n },\n },\n );\n }\n }\n}\n"],"names":["Calendar","soft","parseBrickadiaTime","pattern","explode","time"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"database.js","sources":["../../../src/webserver/backend/database.ts"],"sourcesContent":["import soft from '@/softconfig';\nimport { IServerConfig } from '@config/types';\nimport type Omegga from '@omegga/server';\nimport { explode } from '@util/pattern';\nimport { parseBrickadiaTime } from '@util/time';\nimport bcrypt from 'bcryptjs';\nimport chokidar from 'chokidar';\nimport EventEmitter from 'events';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport Calendar from './calendar';\nimport {\n IPlayer,\n IPunchcard,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreChat,\n IStoreKickHistory,\n IStoreServerInstance,\n IStoreUser,\n IStoreVersion,\n IUserHistory,\n IUserNote,\n} from './types';\n\n// TODO: online users graph\n// TODO: player online times\n// TODO: bricks over time graph\n// TODO: minute server status for metrics\n// TODO: chat messages per min/hour/day check\n\nlet serverInstance: IStoreServerInstance & { _id: string };\n\ntype FixedSizeArray<N extends number, T, M extends string = '0'> = {\n readonly [k in M]: any;\n} & { length: N } & ReadonlyArray<T>;\n\n// generate a punchcard (days x week)\nconst createPunchcard = (): number[][] =>\n Array.from({ length: 7 }).map(() =>\n Array.from<number>({ length: 24 }).fill(0),\n );\n\n// the database keeps track of metrics for omegga\nexport default class Database extends EventEmitter {\n options: IServerConfig;\n omegga: Omegga;\n stores: {\n users: Datastore;\n chat: Datastore;\n players: Datastore;\n status: Datastore;\n server: Datastore;\n };\n calendar: Calendar;\n\n constructor(options: IServerConfig, omegga: Omegga) {\n super();\n this.options = options;\n this.omegga = omegga;\n this.calendar = new Calendar();\n\n // database\n const dbOpts = {\n autoload: true,\n // case insensitive string comparison\n compareStrings: (a: string, b: string) =>\n a.localeCompare(b, 'en', { ignorePunctuation: true }),\n };\n\n // create all the stores\n this.stores = {\n users: Datastore.create({\n filename: path.join(omegga.dataPath, soft.USER_STORE),\n ...dbOpts,\n }),\n chat: Datastore.create({\n filename: path.join(omegga.dataPath, soft.CHAT_STORE),\n ...dbOpts,\n }),\n players: Datastore.create({\n filename: path.join(omegga.dataPath, soft.PLAYER_STORE),\n ...dbOpts,\n }),\n status: Datastore.create({\n filename: path.join(omegga.dataPath, soft.STATUS_STORE),\n ...dbOpts,\n }),\n server: Datastore.create({\n filename: path.join(omegga.dataPath, soft.SERVER_STORE),\n ...dbOpts,\n }),\n };\n }\n\n // make sure all the databases have valid store versions\n async doMigrations() {\n // current store versions\n const storeVersions: { [key: keyof typeof this.stores]: number } = {\n users: 1,\n chat: 1,\n players: 1,\n status: 1,\n server: 1,\n\n // example version\n dummy: 10,\n };\n\n // migrations to/from certain store versions\n const migrations: {\n [key: keyof typeof this.stores]: {\n version: number;\n upgrade: (store: Datastore) => Promise<void>;\n }[];\n } = {\n users: [],\n chat: [],\n players: [],\n status: [],\n server: [],\n\n // example migration list (all version upgrades go up by 1)\n dummy: [\n {\n version: 1, // target version\n upgrade: async _store => {}, // upgrade function (does not include updating version)\n },\n ],\n };\n\n // loop through all stores to find if any need version upgrades\n await Promise.all(\n Object.entries(this.stores).map(async ([name, store]) => {\n const versionEntry = await store.findOne<IStoreVersion>({\n type: 'storeVersion',\n });\n const expected = storeVersions[name];\n // if the version was not found, insert it\n if (!versionEntry) {\n store.insert({ type: 'storeVersion', version: expected });\n\n // if the version was not what was expected\n } else if (expected !== versionEntry.version) {\n // while the version is below the expected version\n let { version } = versionEntry;\n for (; version < expected; version++) {\n // find an available migration\n const migration = migrations[name].find(v => v.version === version);\n\n // no migration found, ignore\n if (!migration) break;\n\n // run the upgrade\n migration.upgrade(store);\n }\n\n // update the stored version\n await store.update({ _id: versionEntry._id }, { version });\n }\n }),\n );\n }\n\n // get the running instance id of this omegga\n async getInstanceId() {\n if (serverInstance) return serverInstance._id;\n\n const doc = await this.stores.server.insert<IStoreServerInstance>({\n type: 'app:start',\n date: Date.now(),\n });\n\n serverInstance = doc;\n\n // create a calendar of all previous chat messages\n const dates = (\n await this.stores.chat.find<IStoreChat>({ type: 'chat' })\n ).map(c => c.created);\n for (const d of dates) {\n this.calendar.addDate(d);\n }\n\n const watcher = chokidar.watch(\n path.join(this.omegga.configPath, 'BanList.json'),\n { persistent: false },\n );\n watcher\n .on('add', () => setTimeout(this.syncBanList.bind(this), 500))\n .on('change', () =>\n setTimeout(() => {\n this.syncBanList();\n this.emit('update.bans');\n }, 500),\n );\n setTimeout(this.syncBanList.bind(this), 500);\n\n const handleKick = (kicked: IPlayer, kicker: IPlayer, reason: string) => {\n // time the event listener should expire\n const eventExpire = Date.now() + 100;\n\n // detect a single leave\n this.omegga.once('leave', async leavingPlayer => {\n const now = Date.now();\n // if a player leaves more than 100ms from the kick command, it's bugged\n if (now > eventExpire) return;\n\n if (leavingPlayer.id === kicked.id) {\n const entry = {\n type: 'kickHistory',\n kicked: kicked.id,\n kickerId: kicker.id,\n created: now,\n reason,\n };\n\n // depending on implementation, this could potentially be a kick event\n await this.stores.players.update(\n entry,\n { $set: entry },\n { upsert: true },\n );\n }\n });\n };\n\n // for a5 is based on the chat event\n this.omegga.on('kick', (kicked, kicker, reason) => {\n const kickedPlayer = this.omegga.findPlayerByName(kicked);\n const kickerPlayer = this.omegga.findPlayerByName(kicker);\n\n // couldn't find kicked or kicker player\n if (!kickedPlayer || !kickerPlayer) return;\n\n handleKick(kickedPlayer, kickerPlayer, reason);\n });\n\n return doc._id;\n }\n\n // synchronize the ban list with the one in the database\n syncBanList() {\n const banList = this.omegga.getBanList()?.banList;\n if (!banList) return;\n\n // upsert all bans\n for (const banned in banList) {\n const entry: IStoreBanHistory = {\n type: 'banHistory',\n banned,\n bannerId: banList[banned].bannerId,\n created: parseBrickadiaTime(banList[banned].created),\n expires: parseBrickadiaTime(banList[banned].expires),\n reason: banList[banned].reason,\n };\n\n this.stores.players.update(entry, { $set: entry }, { upsert: true });\n }\n }\n\n // determine if this user would be the first user (admin user)\n async isFirstUser() {\n return (await this.stores.users.count({ type: 'user' })) === 0;\n }\n\n // hash a password\n async hash(password: string) {\n return await bcrypt.hash(password, 10);\n }\n\n // create an admin user account, username can be blank (no password)\n async createAdminUser(username: string, password: string) {\n const hash = await this.hash(password);\n\n // create an owner user\n const user = this.stores.users.insert({\n // this is a user\n type: 'user',\n created: Date.now(),\n lastOnline: Date.now(),\n\n // credentials\n username,\n hash,\n\n // permissions\n isOwner: true,\n roles: [],\n\n // brickadia player uuid\n playerId: '',\n });\n return user;\n }\n\n // determine if a user with this name exists\n async userExists(username: string) {\n return (await this.stores.users.count({ type: 'user', username })) > 0;\n }\n\n // create a regular user account\n async createUser(username: string, password: string) {\n if (await this.userExists(username)) throw new Error('user already exists');\n\n const hash = await this.hash(password);\n // create an owner user\n const user = this.stores.users.insert<IStoreUser>({\n // this is a user\n type: 'user',\n created: Date.now(),\n lastOnline: 0,\n\n // credentials\n username,\n hash,\n\n // permissions\n isOwner: false,\n roles: [],\n\n // brickadia player uuid\n playerId: '',\n });\n return user;\n }\n\n // set a user's password\n async userPasswd(username: string, password: string) {\n const hash = await this.hash(password);\n\n if (!this.userExists(username)) throw new Error('user does not exist');\n\n await this.stores.users.update(\n { type: 'user', username },\n { $set: { hash } },\n );\n }\n\n // get a user from credentials\n async authUser(username: string, password: string) {\n const user = await this.stores.users.findOne<IStoreUser>({ username });\n // user not found\n if (!user || user.isBanned) return null;\n\n // make sure the user's password hash is valid\n if (await bcrypt.compare(password, user.hash)) {\n // update last online status\n await this.stores.users.update(\n { _id: user._id },\n { $set: { lastOnline: Date.now() } },\n );\n return user;\n }\n\n // wrong password\n return null;\n }\n\n // find a user by object id\n async findUserById(id: string) {\n return await this.stores.users.findOne<IStoreUser>({\n $or: [\n // the owner has no username, so everyone is the owner\n { type: 'user', username: '', isOwner: true },\n\n // the user exists and has an id\n { type: 'user', _id: id },\n ],\n });\n }\n\n // get a list of roles\n // TODO: actually implement roles\n getRoles(): Promise<{ type: 'role'; name: 'string' }[]> {\n return this.stores.server.find({ type: 'role' });\n }\n\n // add a chat message to the chat log store\n async addChatLog(\n action: 'msg' | 'server' | 'leave' | 'join',\n user: IPlayer,\n message?: string,\n ) {\n this.calendar.addDate(Date.now());\n return await this.stores.chat.insert<IStoreChat>({\n type: 'chat',\n created: Date.now(),\n instanceId: await this.getInstanceId(),\n action,\n user,\n ...(message ? { message } : {}),\n });\n }\n\n // get recent chat activity\n async getChats({\n count = 50,\n sameServer,\n before,\n after,\n }: {\n count?: number;\n sameServer?: boolean;\n before?: number;\n after?: number;\n } = {}) {\n return await this.stores.chat\n .find<IStoreChat>({\n type: 'chat',\n ...(sameServer ? { instanceId: await this.getInstanceId() } : {}),\n created: before\n ? { $lt: before }\n : after\n ? { $gt: after }\n : { $lt: Date.now() },\n })\n .sort({ created: !before && after ? 1 : -1 })\n .limit(count)\n .exec();\n }\n\n // get paginated players\n async getPlayers({\n count = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = '1',\n limitId = undefined,\n }: {\n count?: number;\n search?: string;\n page?: number;\n sort?: string;\n direction?: string;\n limitId?: string[];\n } = {}) {\n const pattern = explode(search);\n\n // the query used for finding which players are available\n const query = {\n type: 'userHistory',\n // only filter if there's a query\n $and: [\n // if a limitId is passed in, the id must be in it\n ...(limitId ? [{ id: { $in: limitId } }] : []),\n\n // if there's a search, the search matches...\n ...(search.length > 0\n ? [\n {\n $or: [\n // id was pasted in\n { id: search },\n // user's current name\n { name: { $regex: pattern } },\n { displayName: { $regex: pattern } },\n // user's past name\n {\n nameHistory: { $elemMatch: { name: { $regex: pattern } } },\n },\n {\n nameHistory: {\n $elemMatch: { displayName: { $regex: pattern } },\n },\n },\n ],\n },\n ]\n : []),\n ],\n };\n\n // the query used for finding a specific player\n const exactQuery = {\n type: 'userHistory',\n // only filter if there's a query\n $or: [\n // id was pasted in\n { id: search },\n // user's current name\n { name: search },\n // user's current display name\n { displayName: search },\n ],\n };\n\n // count players and query players\n const [total, players, exactResult] = await Promise.all([\n this.stores.players.count(query),\n this.stores.players\n .find<IUserHistory>(query)\n // TODO: add other sorts and sort directions\n .sort({ [sort]: direction })\n .skip(count * page)\n .limit(count)\n .exec(),\n\n // exact match detection\n search.length > 0 && page === 0\n ? this.stores.players.find(exactQuery)\n : [],\n ]);\n\n // add exact matches to the beginning\n if (exactResult.length > 0) {\n // remove duplicates\n for (const res of exactResult) {\n const index = players.findIndex(p => p._id === res._id);\n players.splice(index, 1);\n }\n\n // add results to the beginning\n players.splice(0, 0, ...exactResult);\n }\n\n return {\n pages: Math.ceil(total / count),\n total,\n players,\n };\n }\n\n // get paginated users\n async getUsers({\n count = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = '1',\n } = {}) {\n const pattern = explode(search);\n\n // the query used for finding which players are available\n const query = {\n type: 'user',\n // only filter if there's a query\n ...(search.length > 0\n ? {\n $or: [\n // id was pasted in\n { playerId: search },\n // user's current name\n { name: { $regex: pattern } },\n ],\n }\n : {}),\n };\n\n // count players and query players\n const [total, users] = await Promise.all([\n this.stores.users.count(query),\n this.stores.users\n .find<IStoreUser>(query)\n // TODO: add other sorts and sort directions\n .sort({ [sort]: direction })\n .skip(count * page)\n .limit(count)\n .exec(),\n ]);\n\n return {\n pages: Math.ceil(total / count),\n total,\n users,\n };\n }\n\n // get an individual player, ban and kick history, and notes\n async getPlayer(id: string) {\n const [player, banHistory, kickHistory, notes] = await Promise.all([\n this.stores.players.findOne<IUserHistory>({ type: 'userHistory', id }),\n this.stores.players\n .find<IStoreBanHistory>({ type: 'banHistory', banned: id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n this.stores.players\n .find<IStoreKickHistory>({ type: 'kickHistory', kicked: id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n this.stores.players\n .find<IUserNote>({ type: 'note', id })\n .sort({ created: -1 })\n .limit(25)\n .exec(),\n ]);\n\n if (!player) return null;\n\n return { ...player, banHistory, kickHistory, notes };\n }\n\n // add a user to the visit history, returns true if this is a first visit\n async addVisit(user: IPlayer) {\n const existing = await this.stores.players.findOne<IUserHistory>({\n type: 'userHistory',\n id: user.id,\n });\n const now = Date.now();\n const instanceId = await this.getInstanceId();\n if (!existing) {\n // create the player\n // TODO: add some form of admin note tool for moderators to leave notes\n // on troublesome players + warnings\n await this.stores.players.insert<IUserHistory>({\n type: 'userHistory',\n // base brickadia user info\n id: user.id,\n name: user.name,\n displayName: user.displayName,\n // list of names and when they were first used\n nameHistory: [\n { name: user.name, displayName: user.displayName, date: now },\n ],\n // list of ips this player has been on\n ips: [],\n // first time player was seen\n created: now,\n // last time player was seen\n lastSeen: now,\n // last server this player has been on\n lastInstanceId: instanceId,\n // number of status checks this player has observed\n heartbeats: 0,\n // number of sessions (at least an hour apart) this player has had\n sessions: 1,\n // number of instances (times omegga was started) this player has joined\n instances: 1,\n });\n return true;\n } else {\n await this.stores.players.update<IUserHistory>(\n { _id: existing._id },\n {\n $inc: {\n // increment number of sessions if the player hasn't been seen for an hour\n sessions: existing.lastSeen < now - 60 * 60 * 1000 ? 1 : 0,\n // increment the number of instances this player has joined\n instances: existing.lastInstanceId === instanceId ? 1 : 0,\n },\n\n $set: {\n // update last seen time and user name\n lastSeen: now,\n name: user.name,\n displayName: user.displayName,\n\n // set the last joined instance\n lastInstanceId: instanceId,\n },\n\n // add the name to the history if it was not already in it\n ...(existing.nameHistory.some(\n h => h.name === user.name && h.displayName === user.displayName,\n )\n ? {}\n : {\n $addToSet: {\n nameHistory: {\n name: user.name,\n displayName: user.displayName,\n date: now,\n },\n },\n }),\n },\n );\n return false;\n }\n }\n\n // use data from minutely heartbeats to fuel metrics\n async addHeartbeat(data: {\n bricks: number;\n players: string[];\n ips: Record<string, string>;\n }) {\n const now = Date.now();\n\n // add the heartbeat to the server status store\n await this.stores.status.insert({\n type: 'heartbeat',\n created: now,\n bricks: data.bricks,\n players: data.players,\n });\n\n // update player heartbeats and last seens\n await this.stores.players.update(\n {\n // all players in the status update\n id: { $in: data.players },\n },\n {\n // update last seen\n $set: { lastSeen: now },\n // increment heartbeats\n $inc: { heartbeats: 1 },\n },\n { multi: true },\n );\n\n // get all players in the status update\n const players = await this.stores.players.find<IUserHistory>({\n id: { $in: data.players },\n });\n\n await Promise.all(\n players\n // find players without the discovered ip\n .filter(p => !(p.ips || []).includes(data.ips[p.id]))\n // insert the IP into the player's ip list\n .map(p =>\n this.stores.players.update(\n { _id: p._id },\n {\n $addToSet: { ips: data.ips[p.id] },\n },\n ),\n ),\n );\n }\n\n async getAutoRestartConfig(): Promise<IStoreAutoRestartConfig> {\n let config = await this.stores.server.findOne<IStoreAutoRestartConfig>({\n type: 'autoRestartConfig',\n });\n if (!config) {\n config = await this.stores.server.insert({\n type: 'autoRestartConfig',\n maxUptime: 48,\n maxUptimeEnabled: false,\n emptyUptime: 24,\n emptyUptimeEnabled: false,\n dailyHour: 2,\n dailyHourEnabled: false,\n autoUpdateEnabled: true,\n autoUpdateIntervalMins: 60,\n announcementEnabled: true,\n playersEnabled: true,\n saveWorld: true,\n crashRestartEnabled: true,\n });\n }\n\n config.autoUpdateEnabled ??= true;\n config.autoUpdateIntervalMins ??= 60;\n config.saveWorld ??= true;\n config.crashRestartEnabled ??= true;\n\n return config;\n }\n\n async setAutoRestartConfig(config: IStoreAutoRestartConfig): Promise<void> {\n config.maxUptime = Math.round(Math.max(1, Math.min(config.maxUptime, 168)));\n config.emptyUptime = Math.round(\n Math.max(1, Math.min(config.emptyUptime, 168)),\n );\n config.dailyHour = Math.round(Math.max(0, Math.min(config.dailyHour, 23)));\n config.autoUpdateIntervalMins = Math.round(\n Math.max(10, Math.min(config.autoUpdateIntervalMins ?? 60, Infinity)),\n );\n config.autoUpdateEnabled ??= true;\n await this.stores.server.update(\n { type: 'autoRestartConfig' },\n {\n $set: {\n ...config,\n },\n },\n { upsert: false },\n );\n }\n\n // update player online-time punchcard\n async updatePlayerPunchcard(numNewPlayers: number) {\n // get date, utc hour, and utc day\n const now = new Date();\n const time = now.getTime();\n const hour = now.getUTCHours();\n const day = now.getUTCDay();\n const month = now.getUTCMonth();\n const year = now.getUTCFullYear();\n\n // find the punchcard\n const card = await this.stores.status.findOne<IPunchcard>({\n type: 'punchcard',\n kind: 'playerCount',\n month,\n year,\n });\n\n // create a new punchcard if one does not exist for this month\n if (!card) {\n const punchcard = createPunchcard();\n punchcard[day][hour] = numNewPlayers;\n // insert it\n await this.stores.status.insert<IPunchcard>({\n type: 'punchcard',\n kind: 'playerCount',\n created: time,\n updated: time,\n month,\n year,\n punchcard,\n });\n\n // if the card exists, check if it was already updated this hour\n } else {\n // add the players to this slot\n card.punchcard[day][hour] += numNewPlayers;\n // update the punchcard\n await this.stores.status.update(\n { _id: card._id },\n {\n $set: {\n punchcard: card.punchcard,\n updated: time,\n },\n },\n );\n }\n }\n}\n"],"names":["Calendar","soft","parseBrickadiaTime","pattern","explode","time"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegga",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Brickadia server installer, updater, manager, wrapper, configurator, and automator",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=23.0.0"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"commander": "^5.1.0",
|
|
22
22
|
"configstore": "^5.0.1",
|
|
23
23
|
"disrequire": "^1.1.1",
|
|
24
|
+
"dotenv": "^17.2.3",
|
|
24
25
|
"express": "^4.18.2",
|
|
25
26
|
"express-session": "^1.17.3",
|
|
26
27
|
"glob": "^7.1.6",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"strip-ansi": "^6.0.1",
|
|
43
44
|
"swc-loader": "^0.2.0",
|
|
44
45
|
"update-notifier-cjs": "^5.1.6",
|
|
45
|
-
"vm2": "3.
|
|
46
|
+
"vm2": "^3.10.3",
|
|
46
47
|
"webpack": "^5.80.0"
|
|
47
48
|
},
|
|
48
49
|
"types": "./dist/lib.d.ts",
|
package/public/app.html
CHANGED
|
@@ -1886,28 +1886,28 @@ a,
|
|
|
1886
1886
|
flex-direction: column;
|
|
1887
1887
|
}
|
|
1888
1888
|
</style>
|
|
1889
|
-
<script type="importmap">{"imports":{"react":"./assets/shared/react-
|
|
1890
|
-
<script type="module" crossorigin src="/assets/app-
|
|
1891
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1892
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1893
|
-
<link rel="modulepreload" crossorigin href="/assets/client-
|
|
1894
|
-
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-
|
|
1895
|
-
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-
|
|
1889
|
+
<script type="importmap">{"imports":{"react":"./assets/shared/react-iXLZ_Bod.js","react-dom":"./assets/shared/react-dom-8Rcmh_FG.js","react/jsx-runtime":"./assets/shared/react_jsx-runtime-BwhiLcBy.js","@nanostores/react":"./assets/shared/@nanostores_react-DJPF1Eqx.js","nanostores":"./assets/shared/nanostores-BFqqIY_c.js","@tabler/icons-react":"./assets/shared/@tabler_icons-react-B4bN8gHc.js","@hooks":"./assets/shared/@hooks-Ch_hJYh0.js","wouter":"./assets/shared/wouter-CUC1caxu.js","@utils":"./assets/shared/@utils-BA2UvC9c.js","@components":"./assets/shared/@components-Bt_lv7Wa.js"}}</script>
|
|
1890
|
+
<script type="module" crossorigin src="/assets/app-S3WcjD4u.js"></script>
|
|
1891
|
+
<link rel="modulepreload" crossorigin href="/assets/index-D0OPOe3j.js">
|
|
1892
|
+
<link rel="modulepreload" crossorigin href="/assets/index-CtCtK5BX.js">
|
|
1893
|
+
<link rel="modulepreload" crossorigin href="/assets/client-BT1QYH0O.js">
|
|
1894
|
+
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-DldvXD6N.js">
|
|
1895
|
+
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BX5r4UiA.js">
|
|
1896
1896
|
<link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-CE1G-McA.js">
|
|
1897
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1897
|
+
<link rel="modulepreload" crossorigin href="/assets/index-CrN8I5Az.js">
|
|
1898
1898
|
<link rel="modulepreload" crossorigin href="/assets/index-CJ4e7_2M.js">
|
|
1899
|
-
<link rel="modulepreload" crossorigin href="/assets/shared/@nanostores_react-
|
|
1899
|
+
<link rel="modulepreload" crossorigin href="/assets/shared/@nanostores_react-DJPF1Eqx.js">
|
|
1900
1900
|
<link rel="modulepreload" crossorigin href="/assets/index-CSV4ZXsq.js">
|
|
1901
|
-
<link rel="modulepreload" crossorigin href="/assets/IconCheck-
|
|
1902
|
-
<link rel="modulepreload" crossorigin href="/assets/IconX-
|
|
1903
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1904
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1905
|
-
<link rel="modulepreload" crossorigin href="/assets/IconWorld-
|
|
1901
|
+
<link rel="modulepreload" crossorigin href="/assets/IconCheck-DOe3u7iH.js">
|
|
1902
|
+
<link rel="modulepreload" crossorigin href="/assets/IconX-CvmO0bbE.js">
|
|
1903
|
+
<link rel="modulepreload" crossorigin href="/assets/index-DY750sgZ.js">
|
|
1904
|
+
<link rel="modulepreload" crossorigin href="/assets/index-2cgXLhrt.js">
|
|
1905
|
+
<link rel="modulepreload" crossorigin href="/assets/IconWorld-B6GK5qpx.js">
|
|
1906
1906
|
<link rel="modulepreload" crossorigin href="/assets/utils-Ds3ldd-U.js">
|
|
1907
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
1908
|
-
<link rel="modulepreload" crossorigin href="/assets/IconWorldPlus-
|
|
1909
|
-
<link rel="modulepreload" crossorigin href="/assets/IconArrowRight-
|
|
1910
|
-
<link rel="modulepreload" crossorigin href="/assets/shared/@hooks-
|
|
1907
|
+
<link rel="modulepreload" crossorigin href="/assets/index-Cz1Unv84.js">
|
|
1908
|
+
<link rel="modulepreload" crossorigin href="/assets/IconWorldPlus-DpfueG8_.js">
|
|
1909
|
+
<link rel="modulepreload" crossorigin href="/assets/IconArrowRight-BuVxhqKX.js">
|
|
1910
|
+
<link rel="modulepreload" crossorigin href="/assets/shared/@hooks-Ch_hJYh0.js">
|
|
1911
1911
|
</head>
|
|
1912
1912
|
<body>
|
|
1913
1913
|
<div id="root"></div>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{c as t}from"./IconCheck-DOe3u7iH.js";import"./index-CrN8I5Az.js";import"./_commonjsHelpers-CE1G-McA.js";import"./index-D0OPOe3j.js";var p=t("outline","arrow-right","IconArrowRight",[["path",{d:"M5 12l14 0",key:"svg-0"}],["path",{d:"M13 18l6 -6",key:"svg-1"}],["path",{d:"M13 6l6 6",key:"svg-2"}]]);export{p as default};
|
|
2
|
+
//# sourceMappingURL=IconArrowRight-BuVxhqKX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconArrowRight-
|
|
1
|
+
{"version":3,"file":"IconArrowRight-BuVxhqKX.js","sources":["../../node_modules/@tabler/icons-react/dist/esm/icons/IconArrowRight.mjs"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.34.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nvar IconArrowRight = createReactComponent(\"outline\", \"arrow-right\", \"IconArrowRight\", [[\"path\", { \"d\": \"M5 12l14 0\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M13 18l6 -6\", \"key\": \"svg-1\" }], [\"path\", { \"d\": \"M13 6l6 6\", \"key\": \"svg-2\" }]]);\n\nexport { IconArrowRight as default };\n//# sourceMappingURL=IconArrowRight.mjs.map\n"],"names":["IconArrowRight","createReactComponent"],"mappings":"2IASG,IAACA,EAAiBC,EAAqB,UAAW,cAAe,iBAAkB,CAAC,CAAC,OAAQ,CAAE,EAAK,aAAc,IAAO,OAAO,CAAE,EAAG,CAAC,OAAQ,CAAE,EAAK,cAAe,IAAO,OAAO,CAAE,EAAG,CAAC,OAAQ,CAAE,EAAK,YAAa,IAAO,OAAO,CAAE,CAAC,CAAC","x_google_ignoreList":[0]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as e}from"./index-CrN8I5Az.js";var f={outline:{xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},filled:{xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"currentColor",stroke:"none"}};const g=(o,i,s,c)=>{const r=e.forwardRef(({color:n="currentColor",size:l=24,stroke:h=2,title:a,className:d,children:t,...u},w)=>e.createElement("svg",{ref:w,...f[o],width:l,height:l,className:["tabler-icon",`tabler-icon-${i}`,d].join(" "),...o==="filled"?{fill:n}:{strokeWidth:h,stroke:n},...u},[a&&e.createElement("title",{key:"svg-title"},a),...c.map(([k,m])=>e.createElement(k,m)),...Array.isArray(t)?t:[t]]));return r.displayName=`${s}`,r};var p=g("outline","check","IconCheck",[["path",{d:"M5 12l5 5l10 -10",key:"svg-0"}]]);const C=Object.freeze(Object.defineProperty({__proto__:null,default:p},Symbol.toStringTag,{value:"Module"}));export{p as I,C as a,g as c};
|
|
2
|
+
//# sourceMappingURL=IconCheck-DOe3u7iH.js.map
|