omegga 1.6.0 → 1.7.0
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 +9 -2
- package/CHANGELOG.md +22 -1
- package/dist/cli/terminal.js +5 -1
- package/dist/cli/terminal.js.map +1 -1
- package/dist/db/connection.js +31 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrate.js +20 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/migrations/0000_long_killer_shrike.sql +111 -0
- package/dist/db/migrations/meta/0000_snapshot.json +737 -0
- package/dist/db/migrations/meta/_journal.json +13 -0
- package/dist/db/nedbImport.js +344 -0
- package/dist/db/nedbImport.js.map +1 -0
- package/dist/db/plugin-migrations/0000_faulty_tyger_tiger.sql +11 -0
- package/dist/db/plugin-migrations/meta/0000_snapshot.json +81 -0
- package/dist/db/plugin-migrations/meta/_journal.json +13 -0
- package/dist/db/pluginSchema.js +19 -0
- package/dist/db/pluginSchema.js.map +1 -0
- package/dist/db/schema.js +174 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/omegga/matchers/version.js +30 -13
- package/dist/omegga/matchers/version.js.map +1 -1
- package/dist/omegga/plugin.js +47 -49
- package/dist/omegga/plugin.js.map +1 -1
- package/dist/omegga/server.js.map +1 -1
- package/dist/softconfig.js +9 -21
- package/dist/softconfig.js.map +1 -1
- package/dist/updater/steam.js +1 -1
- package/dist/updater/steam.js.map +1 -1
- package/dist/util/brdb.js +84 -2
- package/dist/util/brdb.js.map +1 -1
- package/dist/webserver/backend/database.js +533 -533
- package/dist/webserver/backend/database.js.map +1 -1
- package/dist/webserver/backend/index.js +19 -8
- package/dist/webserver/backend/index.js.map +1 -1
- package/dist/webserver/backend/metrics.js +7 -5
- package/dist/webserver/backend/metrics.js.map +1 -1
- package/dist/webserver/backend/permissions.js +69 -20
- package/dist/webserver/backend/permissions.js.map +1 -1
- package/dist/webserver/backend/roleHierarchy.js +106 -0
- package/dist/webserver/backend/roleHierarchy.js.map +1 -0
- package/dist/webserver/backend/router/player.js +13 -16
- package/dist/webserver/backend/router/player.js.map +1 -1
- package/dist/webserver/backend/router/role.js +195 -7
- package/dist/webserver/backend/router/role.js.map +1 -1
- package/dist/webserver/backend/router/server.js +32 -2
- package/dist/webserver/backend/router/server.js.map +1 -1
- package/dist/webserver/backend/router/session.js +3 -5
- package/dist/webserver/backend/router/session.js.map +1 -1
- package/dist/webserver/backend/router/user.js +89 -42
- package/dist/webserver/backend/router/user.js.map +1 -1
- package/dist/webserver/backend/scopes.js +73 -44
- package/dist/webserver/backend/scopes.js.map +1 -1
- package/dist/webserver/backend/trpc.js +3 -6
- package/dist/webserver/backend/trpc.js.map +1 -1
- package/doc/permissions.md +83 -12
- package/drizzle.config.ts +7 -0
- package/drizzle.plugin.config.ts +7 -0
- package/package.json +7 -2
- package/public/app.html +415 -32
- package/public/assets/IconWorld-CmdphDgp.js +2 -0
- package/public/assets/IconWorld-CmdphDgp.js.map +1 -0
- package/public/assets/IconWorldPlus-DKSZjbqh.js +2 -0
- package/public/assets/IconWorldPlus-DKSZjbqh.js.map +1 -0
- package/public/assets/app-BiBsxujC.js +7 -0
- package/public/assets/app-BiBsxujC.js.map +1 -0
- package/public/assets/{auth-CEYiizOt.js → auth-nn5rDnJ9.js} +2 -2
- package/public/assets/{auth-CEYiizOt.js.map → auth-nn5rDnJ9.js.map} +1 -1
- package/public/assets/favicon-DYc6c67x.ico +0 -0
- package/public/assets/{index-DEL-PD_8.js → index-2cgXLhrt.js} +2 -2
- package/public/assets/{index-DEL-PD_8.js.map → index-2cgXLhrt.js.map} +1 -1
- package/public/assets/index-5EkjXNMi.js +12 -0
- package/public/assets/index-5EkjXNMi.js.map +1 -0
- package/public/assets/index-B2DCW1zi.js +2 -0
- package/public/assets/index-B2DCW1zi.js.map +1 -0
- package/public/assets/logo-Dl-LlTow.webp +0 -0
- package/public/assets/shared/@components-VkA0DIz6.js +2 -0
- package/public/assets/shared/@components-VkA0DIz6.js.map +1 -0
- package/public/assets/shared/@hooks-CiJaUL6_.js +2 -0
- package/public/assets/shared/@hooks-CiJaUL6_.js.map +1 -0
- package/public/assets/shared/@tabler_icons-react-B4130bhe.js +3 -0
- package/public/assets/shared/{@tabler_icons-react-DrVFX3uL.js.map → @tabler_icons-react-B4130bhe.js.map} +1 -1
- package/public/assets/shared/{wouter-CNISJ5i6.js → wouter-CUC1caxu.js} +2 -2
- package/public/assets/shared/wouter-CUC1caxu.js.map +1 -0
- package/public/assets/user-XF1UyJMA.js +2 -0
- package/public/assets/user-XF1UyJMA.js.map +1 -0
- package/public/auth.html +163 -16
- package/public/assets/IconWorld-XmQNg6JI.js +0 -2
- package/public/assets/IconWorld-XmQNg6JI.js.map +0 -1
- package/public/assets/IconWorldPlus-CxGGv2eD.js +0 -2
- package/public/assets/IconWorldPlus-CxGGv2eD.js.map +0 -1
- package/public/assets/app-DMr_-wik.js +0 -7
- package/public/assets/app-DMr_-wik.js.map +0 -1
- package/public/assets/index-D_STNela.js +0 -2
- package/public/assets/index-D_STNela.js.map +0 -1
- package/public/assets/index-Dkh3HiBz.js +0 -4
- package/public/assets/index-Dkh3HiBz.js.map +0 -1
- package/public/assets/shared/@components-CgsSYMe6.js +0 -2
- package/public/assets/shared/@components-CgsSYMe6.js.map +0 -1
- package/public/assets/shared/@hooks-DT93N7wd.js +0 -2
- package/public/assets/shared/@hooks-DT93N7wd.js.map +0 -1
- package/public/assets/shared/@tabler_icons-react-DrVFX3uL.js +0 -3
- package/public/assets/shared/wouter-CNISJ5i6.js.map +0 -1
- package/public/assets/useHasScope-BOdFktko.js +0 -2
- package/public/assets/useHasScope-BOdFktko.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const drizzleOrm = require("drizzle-orm");
|
|
4
|
+
const sqliteCore = require("drizzle-orm/sqlite-core");
|
|
5
|
+
const users = sqliteCore.sqliteTable(
|
|
6
|
+
"users",
|
|
7
|
+
{
|
|
8
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
9
|
+
created: sqliteCore.integer("created").notNull(),
|
|
10
|
+
lastOnline: sqliteCore.integer("last_online").notNull().default(0),
|
|
11
|
+
username: sqliteCore.text("username").notNull().unique(),
|
|
12
|
+
hash: sqliteCore.text("hash").notNull(),
|
|
13
|
+
isOwner: sqliteCore.integer("is_owner", { mode: "boolean" }).notNull().default(false),
|
|
14
|
+
roles: sqliteCore.text("roles", { mode: "json" }).notNull().$type().default([]),
|
|
15
|
+
playerId: sqliteCore.text("player_id").notNull().default(""),
|
|
16
|
+
isBanned: sqliteCore.integer("is_banned", { mode: "boolean" }).notNull().default(false),
|
|
17
|
+
permissions: sqliteCore.text("permissions", { mode: "json" }).$type(),
|
|
18
|
+
totpSecret: sqliteCore.text("totp_secret"),
|
|
19
|
+
totpEnabled: sqliteCore.integer("totp_enabled", { mode: "boolean" }).notNull().default(false),
|
|
20
|
+
passkeys: sqliteCore.text("passkeys", { mode: "json" }).notNull().$type().default([]),
|
|
21
|
+
recoveryCodes: sqliteCore.text("recovery_codes", { mode: "json" }).notNull().$type().default([])
|
|
22
|
+
},
|
|
23
|
+
(table) => [
|
|
24
|
+
// NOCASE so the default case-insensitive username sort can use the index
|
|
25
|
+
// (the UNIQUE on username is BINARY and can't serve ORDER BY ... NOCASE)
|
|
26
|
+
sqliteCore.index("users_username_nocase_idx").on(
|
|
27
|
+
drizzleOrm.sql`${table.username} COLLATE NOCASE`
|
|
28
|
+
)
|
|
29
|
+
]
|
|
30
|
+
);
|
|
31
|
+
const chatLogs = sqliteCore.sqliteTable(
|
|
32
|
+
"chat_logs",
|
|
33
|
+
{
|
|
34
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
35
|
+
created: sqliteCore.integer("created").notNull(),
|
|
36
|
+
instanceId: sqliteCore.text("instance_id").notNull(),
|
|
37
|
+
action: sqliteCore.text("action").notNull().$type(),
|
|
38
|
+
user: sqliteCore.text("user", { mode: "json" }).notNull().$type(),
|
|
39
|
+
message: sqliteCore.text("message")
|
|
40
|
+
},
|
|
41
|
+
(table) => [
|
|
42
|
+
sqliteCore.index("chat_logs_created_idx").on(table.created),
|
|
43
|
+
sqliteCore.index("chat_logs_instance_created_idx").on(table.instanceId, table.created)
|
|
44
|
+
]
|
|
45
|
+
);
|
|
46
|
+
const playerHistory = sqliteCore.sqliteTable(
|
|
47
|
+
"player_history",
|
|
48
|
+
{
|
|
49
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
50
|
+
name: sqliteCore.text("name").notNull(),
|
|
51
|
+
displayName: sqliteCore.text("display_name").notNull(),
|
|
52
|
+
nameHistory: sqliteCore.text("name_history", { mode: "json" }).notNull().$type().default([]),
|
|
53
|
+
ips: sqliteCore.text("ips", { mode: "json" }).notNull().$type().default([]),
|
|
54
|
+
created: sqliteCore.integer("created").notNull(),
|
|
55
|
+
lastSeen: sqliteCore.integer("last_seen").notNull(),
|
|
56
|
+
lastInstanceId: sqliteCore.text("last_instance_id").notNull(),
|
|
57
|
+
heartbeats: sqliteCore.integer("heartbeats").notNull().default(0),
|
|
58
|
+
sessions: sqliteCore.integer("sessions").notNull().default(0),
|
|
59
|
+
instances: sqliteCore.integer("instances").notNull().default(0)
|
|
60
|
+
},
|
|
61
|
+
(table) => [
|
|
62
|
+
// NOCASE so the default case-insensitive name sort can use the index
|
|
63
|
+
sqliteCore.index("player_history_name_idx").on(drizzleOrm.sql`${table.name} COLLATE NOCASE`),
|
|
64
|
+
sqliteCore.index("player_history_display_name_idx").on(table.displayName),
|
|
65
|
+
sqliteCore.index("player_history_last_seen_idx").on(table.lastSeen)
|
|
66
|
+
]
|
|
67
|
+
);
|
|
68
|
+
const banHistory = sqliteCore.sqliteTable(
|
|
69
|
+
"ban_history",
|
|
70
|
+
{
|
|
71
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
72
|
+
banned: sqliteCore.text("banned").notNull(),
|
|
73
|
+
bannerId: sqliteCore.text("banner_id").notNull().default(""),
|
|
74
|
+
created: sqliteCore.integer("created").notNull().default(0),
|
|
75
|
+
expires: sqliteCore.integer("expires").notNull().default(0),
|
|
76
|
+
reason: sqliteCore.text("reason").notNull().default("")
|
|
77
|
+
},
|
|
78
|
+
(table) => [
|
|
79
|
+
// includes expires so a ban whose expiry is edited is recorded as a new
|
|
80
|
+
// history entry instead of being dropped by onConflictDoNothing
|
|
81
|
+
sqliteCore.uniqueIndex("ban_history_unique_idx").on(
|
|
82
|
+
table.banned,
|
|
83
|
+
table.bannerId,
|
|
84
|
+
table.created,
|
|
85
|
+
table.expires,
|
|
86
|
+
table.reason
|
|
87
|
+
),
|
|
88
|
+
sqliteCore.index("ban_history_banned_created_idx").on(table.banned, table.created)
|
|
89
|
+
]
|
|
90
|
+
);
|
|
91
|
+
const kickHistory = sqliteCore.sqliteTable(
|
|
92
|
+
"kick_history",
|
|
93
|
+
{
|
|
94
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
95
|
+
kicked: sqliteCore.text("kicked").notNull(),
|
|
96
|
+
kickerId: sqliteCore.text("kicker_id").notNull().default(""),
|
|
97
|
+
created: sqliteCore.integer("created").notNull(),
|
|
98
|
+
reason: sqliteCore.text("reason").notNull().default("")
|
|
99
|
+
},
|
|
100
|
+
(table) => [
|
|
101
|
+
sqliteCore.uniqueIndex("kick_history_unique_idx").on(
|
|
102
|
+
table.kicked,
|
|
103
|
+
table.kickerId,
|
|
104
|
+
table.created,
|
|
105
|
+
table.reason
|
|
106
|
+
),
|
|
107
|
+
sqliteCore.index("kick_history_kicked_created_idx").on(table.kicked, table.created)
|
|
108
|
+
]
|
|
109
|
+
);
|
|
110
|
+
const playerNotes = sqliteCore.sqliteTable(
|
|
111
|
+
"player_notes",
|
|
112
|
+
{
|
|
113
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
114
|
+
playerId: sqliteCore.text("player_id").notNull(),
|
|
115
|
+
note: sqliteCore.text("note").notNull()
|
|
116
|
+
},
|
|
117
|
+
(table) => [sqliteCore.index("player_notes_player_id_idx").on(table.playerId)]
|
|
118
|
+
);
|
|
119
|
+
const serverInstances = sqliteCore.sqliteTable("server_instances", {
|
|
120
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
121
|
+
date: sqliteCore.integer("date").notNull()
|
|
122
|
+
});
|
|
123
|
+
const serverConfig = sqliteCore.sqliteTable("server_config", {
|
|
124
|
+
key: sqliteCore.text("key").primaryKey(),
|
|
125
|
+
value: sqliteCore.text("value", { mode: "json" }).notNull().$type()
|
|
126
|
+
});
|
|
127
|
+
const webRoles = sqliteCore.sqliteTable("web_roles", {
|
|
128
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
129
|
+
name: sqliteCore.text("name").notNull(),
|
|
130
|
+
description: sqliteCore.text("description").notNull().default(""),
|
|
131
|
+
order: sqliteCore.integer("order").notNull(),
|
|
132
|
+
permissions: sqliteCore.text("permissions", { mode: "json" }).notNull().$type()
|
|
133
|
+
});
|
|
134
|
+
const heartbeats = sqliteCore.sqliteTable(
|
|
135
|
+
"heartbeats",
|
|
136
|
+
{
|
|
137
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
138
|
+
created: sqliteCore.integer("created").notNull(),
|
|
139
|
+
bricks: sqliteCore.integer("bricks").notNull(),
|
|
140
|
+
players: sqliteCore.text("players", { mode: "json" }).notNull().$type().default([])
|
|
141
|
+
},
|
|
142
|
+
(table) => [sqliteCore.index("heartbeats_created_idx").on(table.created)]
|
|
143
|
+
);
|
|
144
|
+
const punchcards = sqliteCore.sqliteTable(
|
|
145
|
+
"punchcards",
|
|
146
|
+
{
|
|
147
|
+
id: sqliteCore.integer("id").primaryKey({ autoIncrement: true }),
|
|
148
|
+
kind: sqliteCore.text("kind").notNull(),
|
|
149
|
+
created: sqliteCore.integer("created").notNull(),
|
|
150
|
+
updated: sqliteCore.integer("updated").notNull(),
|
|
151
|
+
month: sqliteCore.integer("month").notNull(),
|
|
152
|
+
year: sqliteCore.integer("year").notNull(),
|
|
153
|
+
punchcard: sqliteCore.text("punchcard", { mode: "json" }).notNull().$type()
|
|
154
|
+
},
|
|
155
|
+
(table) => [
|
|
156
|
+
sqliteCore.uniqueIndex("punchcards_kind_month_year_idx").on(
|
|
157
|
+
table.kind,
|
|
158
|
+
table.month,
|
|
159
|
+
table.year
|
|
160
|
+
)
|
|
161
|
+
]
|
|
162
|
+
);
|
|
163
|
+
exports.banHistory = banHistory;
|
|
164
|
+
exports.chatLogs = chatLogs;
|
|
165
|
+
exports.heartbeats = heartbeats;
|
|
166
|
+
exports.kickHistory = kickHistory;
|
|
167
|
+
exports.playerHistory = playerHistory;
|
|
168
|
+
exports.playerNotes = playerNotes;
|
|
169
|
+
exports.punchcards = punchcards;
|
|
170
|
+
exports.serverConfig = serverConfig;
|
|
171
|
+
exports.serverInstances = serverInstances;
|
|
172
|
+
exports.users = users;
|
|
173
|
+
exports.webRoles = webRoles;
|
|
174
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sources":["../../src/db/schema.ts"],"sourcesContent":["import { sql } from 'drizzle-orm';\nimport {\n index,\n integer,\n sqliteTable,\n text,\n uniqueIndex,\n} from 'drizzle-orm/sqlite-core';\nimport type { PermissionSet } from '@webserver/backend/permissions';\nimport type { IChatUser, IWebAuthnCredential } from '@webserver/backend/types';\n\nexport const users = sqliteTable(\n 'users',\n {\n id: text('id').primaryKey(),\n created: integer('created').notNull(),\n lastOnline: integer('last_online').notNull().default(0),\n username: text('username').notNull().unique(),\n hash: text('hash').notNull(),\n isOwner: integer('is_owner', { mode: 'boolean' }).notNull().default(false),\n roles: text('roles', { mode: 'json' })\n .notNull()\n .$type<string[]>()\n .default([]),\n playerId: text('player_id').notNull().default(''),\n isBanned: integer('is_banned', { mode: 'boolean' })\n .notNull()\n .default(false),\n permissions: text('permissions', { mode: 'json' }).$type<PermissionSet>(),\n totpSecret: text('totp_secret'),\n totpEnabled: integer('totp_enabled', { mode: 'boolean' })\n .notNull()\n .default(false),\n passkeys: text('passkeys', { mode: 'json' })\n .notNull()\n .$type<IWebAuthnCredential[]>()\n .default([]),\n recoveryCodes: text('recovery_codes', { mode: 'json' })\n .notNull()\n .$type<string[]>()\n .default([]),\n },\n table => [\n // NOCASE so the default case-insensitive username sort can use the index\n // (the UNIQUE on username is BINARY and can't serve ORDER BY ... NOCASE)\n index('users_username_nocase_idx').on(\n sql`${table.username} COLLATE NOCASE`,\n ),\n ],\n);\n\nexport const chatLogs = sqliteTable(\n 'chat_logs',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n created: integer('created').notNull(),\n instanceId: text('instance_id').notNull(),\n action: text('action')\n .notNull()\n .$type<'msg' | 'server' | 'leave' | 'join'>(),\n user: text('user', { mode: 'json' }).notNull().$type<Partial<IChatUser>>(),\n message: text('message'),\n },\n table => [\n index('chat_logs_created_idx').on(table.created),\n index('chat_logs_instance_created_idx').on(table.instanceId, table.created),\n ],\n);\n\nexport const playerHistory = sqliteTable(\n 'player_history',\n {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n displayName: text('display_name').notNull(),\n nameHistory: text('name_history', { mode: 'json' })\n .notNull()\n .$type<{ displayName: string; name: string; date: number }[]>()\n .default([]),\n ips: text('ips', { mode: 'json' }).notNull().$type<string[]>().default([]),\n created: integer('created').notNull(),\n lastSeen: integer('last_seen').notNull(),\n lastInstanceId: text('last_instance_id').notNull(),\n heartbeats: integer('heartbeats').notNull().default(0),\n sessions: integer('sessions').notNull().default(0),\n instances: integer('instances').notNull().default(0),\n },\n table => [\n // NOCASE so the default case-insensitive name sort can use the index\n index('player_history_name_idx').on(sql`${table.name} COLLATE NOCASE`),\n index('player_history_display_name_idx').on(table.displayName),\n index('player_history_last_seen_idx').on(table.lastSeen),\n ],\n);\n\nexport const banHistory = sqliteTable(\n 'ban_history',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n banned: text('banned').notNull(),\n bannerId: text('banner_id').notNull().default(''),\n created: integer('created').notNull().default(0),\n expires: integer('expires').notNull().default(0),\n reason: text('reason').notNull().default(''),\n },\n table => [\n // includes expires so a ban whose expiry is edited is recorded as a new\n // history entry instead of being dropped by onConflictDoNothing\n uniqueIndex('ban_history_unique_idx').on(\n table.banned,\n table.bannerId,\n table.created,\n table.expires,\n table.reason,\n ),\n index('ban_history_banned_created_idx').on(table.banned, table.created),\n ],\n);\n\nexport const kickHistory = sqliteTable(\n 'kick_history',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n kicked: text('kicked').notNull(),\n kickerId: text('kicker_id').notNull().default(''),\n created: integer('created').notNull(),\n reason: text('reason').notNull().default(''),\n },\n table => [\n uniqueIndex('kick_history_unique_idx').on(\n table.kicked,\n table.kickerId,\n table.created,\n table.reason,\n ),\n index('kick_history_kicked_created_idx').on(table.kicked, table.created),\n ],\n);\n\nexport const playerNotes = sqliteTable(\n 'player_notes',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n playerId: text('player_id').notNull(),\n note: text('note').notNull(),\n },\n table => [index('player_notes_player_id_idx').on(table.playerId)],\n);\n\nexport const serverInstances = sqliteTable('server_instances', {\n id: text('id').primaryKey(),\n date: integer('date').notNull(),\n});\n\nexport const serverConfig = sqliteTable('server_config', {\n key: text('key').primaryKey(),\n value: text('value', { mode: 'json' }).notNull().$type<unknown>(),\n});\n\nexport const webRoles = sqliteTable('web_roles', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n description: text('description').notNull().default(''),\n order: integer('order').notNull(),\n permissions: text('permissions', { mode: 'json' })\n .notNull()\n .$type<PermissionSet>(),\n});\n\nexport const heartbeats = sqliteTable(\n 'heartbeats',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n created: integer('created').notNull(),\n bricks: integer('bricks').notNull(),\n players: text('players', { mode: 'json' })\n .notNull()\n .$type<string[]>()\n .default([]),\n },\n table => [index('heartbeats_created_idx').on(table.created)],\n);\n\nexport const punchcards = sqliteTable(\n 'punchcards',\n {\n id: integer('id').primaryKey({ autoIncrement: true }),\n kind: text('kind').notNull(),\n created: integer('created').notNull(),\n updated: integer('updated').notNull(),\n month: integer('month').notNull(),\n year: integer('year').notNull(),\n punchcard: text('punchcard', { mode: 'json' })\n .notNull()\n .$type<number[][]>(),\n },\n table => [\n uniqueIndex('punchcards_kind_month_year_idx').on(\n table.kind,\n table.month,\n table.year,\n ),\n ],\n);\n"],"names":["sqliteTable","text","integer","index","sql","uniqueIndex"],"mappings":"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"}
|
|
@@ -17,24 +17,41 @@ const version = (omegga) => {
|
|
|
17
17
|
);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const tryReadVersion = async () => {
|
|
21
|
+
const stream = fs.createReadStream(LOG_PATH, {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
start: 0,
|
|
24
|
+
end: 100
|
|
25
|
+
});
|
|
26
26
|
let data = "";
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
try {
|
|
28
|
+
for await (const chunk of stream) {
|
|
29
|
+
data += chunk;
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
29
33
|
}
|
|
30
34
|
for (const line2 of data.split("\n")) {
|
|
31
35
|
const match = line2.match(versionRegExp);
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
if (match) return Number(match.groups?.version);
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
(async () => {
|
|
41
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
42
|
+
const version2 = await tryReadVersion();
|
|
43
|
+
if (version2 != null) {
|
|
44
|
+
omegga.emit("version", version2);
|
|
45
|
+
logger.default.verbose("Brickadia Version", version2);
|
|
46
|
+
omegga.version = version2;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
37
50
|
}
|
|
51
|
+
logger.default.warnp(
|
|
52
|
+
"Could not determine Brickadia version from",
|
|
53
|
+
LOG_PATH.yellow
|
|
54
|
+
);
|
|
38
55
|
})();
|
|
39
56
|
return 1;
|
|
40
57
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../../../src/omegga/matchers/version.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { MatchGenerator } from './types';\nimport path from 'path';\nimport { createReadStream, existsSync } from 'node:fs';\n\nconst versionRegExp =\n /Brickadia (?<branchName>.+?) \\(.+-CL(?<version>\\d+)\\), Engine (?<engineVersion>.+)/;\n\nconst version: MatchGenerator<number> = omegga => {\n const LOG_PATH = path.join(omegga.dataPath, 'Saved/Logs/Brickadia.log');\n\n return {\n pattern(line, _logMatch) {\n if (!line.startsWith('LogPakFile')) return;\n if (line !== 'LogPakFile: Initializing PakPlatformFile') return;\n\n // The version line\n // Brickadia Release-EA1 (PC-Shipping-CL11633), Engine 444a09f18f48\n // is not printed in the game logs.. only the log file...\n\n if (!existsSync(LOG_PATH)) {\n Logger.warnp(\n 'Log file not found',\n LOG_PATH.yellow + '. Cannot check version.',\n );\n return;\n }\n\n //
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../../../src/omegga/matchers/version.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { MatchGenerator } from './types';\nimport path from 'path';\nimport { createReadStream, existsSync } from 'node:fs';\n\nconst versionRegExp =\n /Brickadia (?<branchName>.+?) \\(.+-CL(?<version>\\d+)\\), Engine (?<engineVersion>.+)/;\n\nconst version: MatchGenerator<number> = omegga => {\n const LOG_PATH = path.join(omegga.dataPath, 'Saved/Logs/Brickadia.log');\n\n return {\n pattern(line, _logMatch) {\n if (!line.startsWith('LogPakFile')) return;\n if (line !== 'LogPakFile: Initializing PakPlatformFile') return;\n\n // The version line\n // Brickadia Release-EA1 (PC-Shipping-CL11633), Engine 444a09f18f48\n // is not printed in the game logs.. only the log file...\n\n if (!existsSync(LOG_PATH)) {\n Logger.warnp(\n 'Log file not found',\n LOG_PATH.yellow + '. Cannot check version.',\n );\n return;\n }\n\n // The version line lives only in the log file (not stdout), near the very\n // top. We trigger off a stdout line, but the file may not be flushed to\n // disk that exact instant - so read the first 100 chars and, if the\n // version line isn't there yet, retry a couple times to win that race.\n const tryReadVersion = async (): Promise<number | null> => {\n const stream = createReadStream(LOG_PATH, {\n encoding: 'utf8',\n start: 0,\n end: 100,\n });\n\n let data = '';\n try {\n for await (const chunk of stream) {\n data += chunk;\n }\n } catch {\n return null;\n }\n\n for (const line of data.split('\\n')) {\n const match = line.match(versionRegExp);\n if (match) return Number(match.groups?.version);\n }\n return null;\n };\n\n (async () => {\n // initial attempt + 2 retries; the version is at the start of the log\n for (let attempt = 0; attempt < 3; attempt++) {\n const version = await tryReadVersion();\n if (version != null) {\n omegga.emit('version', version);\n Logger.verbose('Brickadia Version', version);\n omegga.version = version;\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 250));\n }\n Logger.warnp(\n 'Could not determine Brickadia version from',\n LOG_PATH.yellow,\n );\n })();\n\n return 1;\n },\n callback(_version) {},\n };\n};\n\nexport default version;\n"],"names":["existsSync","Logger","createReadStream","line","version"],"mappings":";;;;;AAKA,MAAM,gBACJ;AAEF,MAAM,UAAkC,CAAA,WAAU;AAChD,QAAM,WAAW,KAAK,KAAK,OAAO,UAAU,0BAA0B;AAEtE,SAAO;AAAA,IACL,QAAQ,MAAM,WAAW;AACvB,UAAI,CAAC,KAAK,WAAW,YAAY,EAAG;AACpC,UAAI,SAAS,2CAA4C;AAMzD,UAAI,CAACA,GAAAA,WAAW,QAAQ,GAAG;AACzBC,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,SAAS,SAAS;AAAA,QAAA;AAEpB;AAAA,MACF;AAMA,YAAM,iBAAiB,YAAoC;AACzD,cAAM,SAASC,GAAAA,iBAAiB,UAAU;AAAA,UACxC,UAAU;AAAA,UACV,OAAO;AAAA,UACP,KAAK;AAAA,QAAA,CACN;AAED,YAAI,OAAO;AACX,YAAI;AACF,2BAAiB,SAAS,QAAQ;AAChC,oBAAQ;AAAA,UACV;AAAA,QACF,QAAQ;AACN,iBAAO;AAAA,QACT;AAEA,mBAAWC,SAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,gBAAM,QAAQA,MAAK,MAAM,aAAa;AACtC,cAAI,MAAO,QAAO,OAAO,MAAM,QAAQ,OAAO;AAAA,QAChD;AACA,eAAO;AAAA,MACT;AAEA,OAAC,YAAY;AAEX,iBAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,gBAAMC,WAAU,MAAM,eAAA;AACtB,cAAIA,YAAW,MAAM;AACnB,mBAAO,KAAK,WAAWA,QAAO;AAC9BH,2BAAO,QAAQ,qBAAqBG,QAAO;AAC3C,mBAAO,UAAUA;AACjB;AAAA,UACF;AACA,gBAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,GAAG,CAAC;AAAA,QACvD;AACAH,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,SAAS;AAAA,QAAA;AAAA,MAEb,GAAA;AAEA,aAAO;AAAA,IACT;AAAA,IACA,SAAS,UAAU;AAAA,IAAC;AAAA,EAAA;AAExB;;"}
|
package/dist/omegga/plugin.js
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const logger = require("../logger.js");
|
|
4
4
|
const softconfig = require("../softconfig.js");
|
|
5
|
-
const
|
|
5
|
+
const connection = require("../db/connection.js");
|
|
6
|
+
const migrate = require("../db/migrate.js");
|
|
7
|
+
const nedbImport = require("../db/nedbImport.js");
|
|
8
|
+
const pluginSchema = require("../db/pluginSchema.js");
|
|
9
|
+
const drizzleOrm = require("drizzle-orm");
|
|
10
|
+
const betterSqlite3 = require("drizzle-orm/better-sqlite3");
|
|
6
11
|
const fs = require("node:fs");
|
|
7
12
|
const path = require("node:path");
|
|
8
13
|
const plugin_jsonrpc_stdio = require("./plugin/plugin_jsonrpc_stdio.js");
|
|
9
14
|
const plugin_node_safe = require("./plugin/plugin_node_safe.js");
|
|
10
15
|
const plugin_node_unsafe = require("./plugin/plugin_node_unsafe.js");
|
|
11
16
|
class PluginStorage {
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
17
|
+
constructor(db, plugin) {
|
|
18
|
+
this.db = db;
|
|
14
19
|
this.plugin = plugin;
|
|
15
20
|
this.name = plugin.getName();
|
|
16
21
|
}
|
|
@@ -27,29 +32,20 @@ class PluginStorage {
|
|
|
27
32
|
}
|
|
28
33
|
// set the config field for this plugin
|
|
29
34
|
async setConfig(value) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
{ upsert: true }
|
|
36
|
-
);
|
|
35
|
+
this.db.insert(pluginSchema.pluginConfig).values({ plugin: this.name, value }).onConflictDoUpdate({
|
|
36
|
+
target: pluginSchema.pluginConfig.plugin,
|
|
37
|
+
set: { value }
|
|
38
|
+
}).run();
|
|
37
39
|
}
|
|
38
40
|
// get the config for this plugin
|
|
39
41
|
async getConfig() {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
44
|
-
if (!config) return null;
|
|
45
|
-
return config.value;
|
|
42
|
+
const row = this.db.select().from(pluginSchema.pluginConfig).where(drizzleOrm.eq(pluginSchema.pluginConfig.plugin, this.name)).get();
|
|
43
|
+
if (!row) return null;
|
|
44
|
+
return row.value;
|
|
46
45
|
}
|
|
47
46
|
/** Wipes the configs for the plugin. */
|
|
48
47
|
async wipeConfig() {
|
|
49
|
-
|
|
50
|
-
{ type: "config", plugin: this.name },
|
|
51
|
-
{ multi: true }
|
|
52
|
-
);
|
|
48
|
+
this.db.delete(pluginSchema.pluginConfig).where(drizzleOrm.eq(pluginSchema.pluginConfig.plugin, this.name)).run();
|
|
53
49
|
}
|
|
54
50
|
/** Initializes the configs for the plugin into the database and sets default configs. */
|
|
55
51
|
async init() {
|
|
@@ -65,57 +61,58 @@ class PluginStorage {
|
|
|
65
61
|
// get a stored object
|
|
66
62
|
async get(key) {
|
|
67
63
|
if (typeof key !== "string" || key.length === 0) return;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
64
|
+
const row = this.db.select().from(pluginSchema.pluginStore).where(
|
|
65
|
+
drizzleOrm.and(
|
|
66
|
+
drizzleOrm.eq(pluginSchema.pluginStore.plugin, this.name),
|
|
67
|
+
drizzleOrm.eq(pluginSchema.pluginStore.key, key)
|
|
68
|
+
)
|
|
69
|
+
).get();
|
|
70
|
+
if (!row) return null;
|
|
71
|
+
return row.value;
|
|
75
72
|
}
|
|
76
73
|
// delete a stored object
|
|
77
74
|
async delete(key) {
|
|
78
75
|
if (typeof key !== "string" || key.length === 0) return;
|
|
79
|
-
|
|
76
|
+
this.db.delete(pluginSchema.pluginStore).where(
|
|
77
|
+
drizzleOrm.and(
|
|
78
|
+
drizzleOrm.eq(pluginSchema.pluginStore.plugin, this.name),
|
|
79
|
+
drizzleOrm.eq(pluginSchema.pluginStore.key, key)
|
|
80
|
+
)
|
|
81
|
+
).run();
|
|
80
82
|
}
|
|
81
83
|
// clear all stored values
|
|
82
84
|
async wipe() {
|
|
83
|
-
|
|
84
|
-
{ type: "store", plugin: this.name },
|
|
85
|
-
{ multi: true }
|
|
86
|
-
);
|
|
85
|
+
this.db.delete(pluginSchema.pluginStore).where(drizzleOrm.eq(pluginSchema.pluginStore.plugin, this.name)).run();
|
|
87
86
|
}
|
|
88
87
|
// count number of objects in store
|
|
89
88
|
async count() {
|
|
90
|
-
|
|
89
|
+
const result = this.db.select({ count: drizzleOrm.count() }).from(pluginSchema.pluginStore).where(drizzleOrm.eq(pluginSchema.pluginStore.plugin, this.name)).get();
|
|
90
|
+
return result?.count ?? 0;
|
|
91
91
|
}
|
|
92
92
|
// get keys of all objects in store
|
|
93
93
|
async keys() {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
plugin: this.name
|
|
97
|
-
});
|
|
98
|
-
return objects.map((o) => o.key);
|
|
94
|
+
const rows = this.db.select({ key: pluginSchema.pluginStore.key }).from(pluginSchema.pluginStore).where(drizzleOrm.eq(pluginSchema.pluginStore.plugin, this.name)).all();
|
|
95
|
+
return rows.map((r) => r.key);
|
|
99
96
|
}
|
|
100
97
|
// set a stored value
|
|
101
98
|
async set(key, value) {
|
|
102
99
|
if (typeof key !== "string" || key.length === 0) return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
if (value == null || JSON.stringify(value) === void 0)
|
|
101
|
+
return this.delete(key);
|
|
102
|
+
this.db.insert(pluginSchema.pluginStore).values({ plugin: this.name, key, value }).onConflictDoUpdate({
|
|
103
|
+
target: [pluginSchema.pluginStore.plugin, pluginSchema.pluginStore.key],
|
|
104
|
+
set: { value }
|
|
105
|
+
}).run();
|
|
108
106
|
}
|
|
109
107
|
}
|
|
110
108
|
class PluginLoader {
|
|
111
109
|
constructor(workDir, omegga) {
|
|
112
110
|
this.path = path.join(workDir, softconfig.default.PLUGIN_PATH);
|
|
113
111
|
this.omegga = omegga;
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.store.persistence.setAutocompactionInterval(1e3 * 60 * 5);
|
|
112
|
+
this.dataPath = path.join(workDir, softconfig.default.DATA_PATH);
|
|
113
|
+
const sqlite = connection.openDb(path.join(this.dataPath, softconfig.default.PLUGINS_DB));
|
|
114
|
+
this.pluginDb = betterSqlite3.drizzle(sqlite);
|
|
115
|
+
migrate.runPluginMigrations(this.pluginDb);
|
|
119
116
|
this.plugins = [];
|
|
120
117
|
logger.default.verbose("Loading plugin formats");
|
|
121
118
|
this.formats = [plugin_jsonrpc_stdio.default, plugin_node_safe.default, plugin_node_unsafe.default];
|
|
@@ -195,6 +192,7 @@ class PluginLoader {
|
|
|
195
192
|
/** Scans a plugin at the specified directory and create a Plugin object. */
|
|
196
193
|
async scanPlugin(dir) {
|
|
197
194
|
logger.default.verbose("Scanning plugin", dir.underline);
|
|
195
|
+
await nedbImport.importNedbIfNeeded(this.dataPath);
|
|
198
196
|
if (!fs.existsSync(dir)) {
|
|
199
197
|
logger.default.errorp("Plugin directory does not exist", dir.brightRed.underline);
|
|
200
198
|
return;
|
|
@@ -214,7 +212,7 @@ class PluginLoader {
|
|
|
214
212
|
logger.default.errorp("Missing/invalid plugin documentation for", dir);
|
|
215
213
|
return;
|
|
216
214
|
}
|
|
217
|
-
const storage = new PluginStorage(this.
|
|
215
|
+
const storage = new PluginStorage(this.pluginDb, plugin);
|
|
218
216
|
await storage.init();
|
|
219
217
|
plugin.setStorage(storage);
|
|
220
218
|
return plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../../src/omegga/plugin.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { IPluginCommand, IPluginDocumentation } from '@/plugin';\nimport soft from '@/softconfig';\nimport Datastore from 'nedb-promises';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type Player from './player';\nimport { Plugin } from './plugin/interface';\nimport RpcPlugin from './plugin/plugin_jsonrpc_stdio';\nimport NodeVmPlugin from './plugin/plugin_node_safe';\nimport NodePlugin from './plugin/plugin_node_unsafe';\nimport type Omegga from './server';\n\nexport interface IPluginJSON {\n formatVersion: number;\n omeggaVersion: string;\n emitConfig?: string;\n dependencies?: {\n [pluginName: string]: { optional?: boolean; repo?: string } | string;\n };\n loadPriority?: number;\n loadBefore?: string[];\n loadAfter?: string[];\n}\n\n// TODO: move doc.json to this file (maybe)\n// TODO: cleaner plugin error messages\n\nexport interface IStoreItem<T = unknown> {\n type: 'store';\n key: string;\n plugin: string;\n value: T;\n}\n\nexport interface IStoreConfig {\n type: 'config';\n plugin: string;\n value: Record<string, unknown>;\n}\n\n// key-value storage for a plugin\nexport class PluginStorage {\n store: Datastore;\n plugin: Plugin;\n name: string;\n\n constructor(store: Datastore, plugin: Plugin) {\n this.store = store;\n this.plugin = plugin;\n this.name = plugin.getName();\n }\n\n // get the initial config values\n getDefaultConfig() {\n const doc = this.plugin.getDocumentation();\n // check if the documentation object exists and whether it has a config field\n if (!doc) return {};\n if (!doc.config) return {};\n\n // insert values from default config values\n const config: Record<string, unknown> = {};\n for (const k in doc.config) {\n config[k] = doc.config[k].default;\n }\n\n return config;\n }\n\n // set the config field for this plugin\n async setConfig(value: unknown) {\n await this.store.update(\n { type: 'config', plugin: this.name },\n {\n $set: { value },\n },\n { upsert: true },\n );\n }\n\n // get the config for this plugin\n async getConfig(): Promise<Record<string, unknown>> {\n const config = await this.store.findOne<IStoreConfig>({\n type: 'config',\n plugin: this.name,\n });\n if (!config) return null;\n return config.value;\n }\n\n /** Wipes the configs for the plugin. */\n async wipeConfig() {\n await this.store.remove(\n { type: 'config', plugin: this.name },\n { multi: true },\n );\n }\n\n /** Initializes the configs for the plugin into the database and sets default configs. */\n async init() {\n // get default and configured values\n const defaultConf = this.getDefaultConfig();\n const config = await this.getConfig();\n\n await this.setConfig({\n // use default config\n ...defaultConf,\n\n // override with actual config values\n ...(config ? config : {}),\n });\n }\n\n // get a stored object\n async get<T>(key: string) {\n if (typeof key !== 'string' || key.length === 0) return;\n const obj = await this.store.findOne<IStoreItem<T>>({\n type: 'store',\n plugin: this.name,\n key,\n });\n if (!obj) return null;\n return obj.value;\n }\n\n // delete a stored object\n async delete(key: string) {\n if (typeof key !== 'string' || key.length === 0) return;\n await this.store.remove({ type: 'store', plugin: this.name, key }, {});\n }\n\n // clear all stored values\n async wipe() {\n await this.store.remove(\n { type: 'store', plugin: this.name },\n { multi: true },\n );\n }\n\n // count number of objects in store\n async count() {\n return await this.store.count({ type: 'store', plugin: this.name });\n }\n\n // get keys of all objects in store\n async keys() {\n const objects = await this.store.find<IStoreItem>({\n type: 'store',\n plugin: this.name,\n });\n return objects.map(o => o.key);\n }\n\n // set a stored value\n async set<T = unknown>(key: string, value: T) {\n if (typeof key !== 'string' || key.length === 0) return;\n await this.store.update<IStoreItem>(\n { type: 'store', plugin: this.name, key },\n { $set: { value } },\n { upsert: true },\n );\n }\n}\n\n/*\n Plugin Loader and Scanner\n Allows omegga to scan in plugins and load them\n*/\nexport class PluginLoader {\n path: string;\n omegga: Omegga;\n store: Datastore;\n formats: (typeof Plugin)[];\n plugins: Plugin[];\n\n commands: Record<string, IPluginCommand & { _plugin: Plugin }>;\n documentation: Record<string, IPluginDocumentation & { _plugin: Plugin }>;\n\n constructor(workDir: string, omegga?: Omegga) {\n this.path = path.join(workDir, soft.PLUGIN_PATH);\n this.omegga = omegga;\n this.store = Datastore.create({\n filename: path.join(workDir, soft.DATA_PATH, soft.PLUGIN_STORE),\n autoload: true,\n });\n this.store.persistence.setAutocompactionInterval(1000 * 60 * 5);\n this.plugins = [];\n\n Logger.verbose('Loading plugin formats');\n this.formats = [RpcPlugin, NodeVmPlugin, NodePlugin];\n Logger.verbose('Found plugin formats:', this.formats);\n\n if (omegga) {\n // soon to be deprecated !help\n omegga.on('chatcmd:help', this.showHelp.bind(this));\n\n // use /plugins to get help\n omegga.on('cmd:plugins', this.showHelp.bind(this));\n }\n }\n\n // determine if this command is a command on the plugin\n isCommand(cmd: string) {\n return cmd === 'plugins' || this.plugins.some(p => p.isCommand(cmd));\n }\n\n // unload and load all installed plugins\n async reload() {\n let ok = true;\n Logger.verbose('Reloading plugins');\n for (const p of this.plugins) {\n try {\n // unload the plugin if it's loaded\n if (p.isLoaded()) await p.unload();\n\n // load the plugin if it successfully unloaded\n if (!p.isLoaded()) {\n // only load it if it is enabled\n if (p.isEnabled()) {\n Logger.verbose(\n 'Loading plugin',\n (p.constructor as typeof Plugin).getFormat(),\n p.getName().underline,\n );\n ok = (await p.load()) || ok;\n }\n } else {\n Logger.errorp(\n 'Did not successfully unload plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n }\n } catch (err) {\n Logger.errorp(\n 'Error loading plugin',\n p.getName().brightRed.underline,\n err,\n );\n ok = false;\n }\n }\n Logger.verbose('Finished reloading plugins');\n return ok;\n }\n\n // stop all plugins from running\n async unload() {\n Logger.verbose('Unloading plugins');\n let ok = true;\n // potentually use Promise.all\n for (const p of this.plugins) {\n try {\n // unload the plugin if it's loaded\n if (p.isLoaded()) {\n if (!(await p.unload())) {\n Logger.errorp(\n 'Could not unloading plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n continue;\n } else {\n Logger.verbose('Unloaded', p.getName().underline);\n }\n }\n } catch (e) {\n Logger.errorp(\n 'Error unloading plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n }\n }\n return ok;\n }\n\n /** Scans a plugin at the specified directory and create a Plugin object. */\n async scanPlugin(dir: string): Promise<Plugin | undefined> {\n Logger.verbose('Scanning plugin', dir.underline);\n\n if (!fs.existsSync(dir)) {\n Logger.errorp('Plugin directory does not exist', dir.brightRed.underline);\n return;\n }\n if (!fs.statSync(dir).isDirectory()) {\n Logger.errorp('Plugin must be a directory', dir.brightRed.underline);\n return;\n }\n\n // find a plugin format that can load in this plugin\n const PluginFormat = this.formats.find(f => f.canLoad(dir));\n\n // let users know if there's a missing plugin format\n if (!PluginFormat) {\n Logger.errorp('Missing plugin format for', dir);\n return;\n }\n try {\n // create the plugin format\n const plugin = PluginFormat && new PluginFormat(dir, this.omegga);\n if (!plugin.getDocumentation()) {\n Logger.errorp('Missing/invalid plugin documentation for', dir);\n return;\n }\n\n // create its storage\n const storage = new PluginStorage(this.store, plugin);\n await storage.init();\n\n // load the storage in\n plugin.setStorage(storage);\n\n // if there is a plugin format, create the plugin instance (but don't load yet)\n return plugin;\n } catch (e) {\n // if a plugin format fails to load, prevent omegga from dying\n Logger.errorp(\n 'Error loading plugin',\n dir.brightRed.underline,\n PluginFormat,\n e,\n );\n }\n }\n\n static calculateLoadOrder(unsortedPlugins: Plugin[]) {\n if (!unsortedPlugins.length) {\n return [];\n }\n\n // create a DAG of plugins based on dependencies, loadBefore, and loadAfter\n type DependencyGraphNode = {\n plugin: Plugin;\n inDegree: number;\n behind: string[];\n };\n type DependencyGraph = Record<string, DependencyGraphNode>;\n\n const depGraph = unsortedPlugins.reduce((graph, plugin) => {\n const name = plugin.getName();\n const {\n dependencies = {},\n loadAfter = [],\n loadBefore = [],\n } = plugin.pluginConfig ?? {};\n\n //create a node in case it doesn't exist\n graph[name] ??= {} as DependencyGraphNode;\n graph[name].plugin ??= plugin;\n graph[name].behind ??= [];\n graph[name].inDegree ??= 0;\n\n // add edges for everything ahead of name\n for (const depName of [...loadAfter, ...Object.keys(dependencies)]) {\n graph[name].behind.push(depName);\n\n //create a node and set an inDegree in case it doesn't exist\n graph[depName] ??= {} as DependencyGraphNode;\n graph[depName].inDegree ??= 0;\n\n graph[depName].inDegree++;\n }\n\n // add edges for everything behind name\n for (const depName of [...loadBefore]) {\n //create a node and a behind array in case it doesn't exist\n graph[depName] ??= {} as DependencyGraphNode;\n graph[depName].behind ??= [];\n\n graph[depName].behind.push(name);\n\n graph[name].inDegree++;\n }\n\n return graph;\n }, {} as DependencyGraph);\n\n // find all nodes with no incoming edges to start processing\n let zeros: string[] = Object.entries(depGraph).reduce(\n (arr, [name, { inDegree }]) => {\n if (inDegree === 0) {\n arr.push(name);\n }\n return arr;\n },\n [],\n );\n\n // execute a topological sort, grouping nodes where the order doesn't matter, in the order that *does* matter\n const result: string[][] = [];\n while (zeros.length) {\n // these nodes can be in any order\n result.unshift([...zeros]);\n\n const nextZero = [];\n\n for (const node of zeros) {\n for (const neighbor of depGraph[node].behind) {\n depGraph[neighbor].inDegree--;\n if (depGraph[neighbor].inDegree === 0) {\n nextZero.push(neighbor);\n }\n }\n }\n\n zeros = nextZero;\n }\n\n if (Object.values(depGraph).some(({ inDegree }) => inDegree > 0)) {\n Logger.verbose(\n 'Cyclic or missing dependencies detected in plugins. Could not resolve load order.',\n );\n return [];\n }\n\n return result\n .map(interchangeablePlugins =>\n interchangeablePlugins\n .map(name => depGraph[name].plugin)\n // alphabetical order for the sub groups where order doesn't matter so its deterministic\n .sort((a, b) => a.getName().localeCompare(b.getName())),\n )\n .flat()\n .sort((a, b) => {\n const A = a.pluginConfig?.loadPriority;\n const B = b.pluginConfig?.loadPriority;\n const aU = A == null;\n const bU = B == null;\n // sort the result by load priority so that\n // a defined loadPriority that is negative comes before undefined (lower is earlier)\n // and a defined loadPriority that is positive comes after undefined (higher is later)\n if (aU && bU) return 0;\n if (aU) return B <= 0 ? 1 : -1;\n if (bU) return A <= 0 ? -1 : 1;\n return A - B;\n });\n }\n\n /** Scans plugin directory for plugins and builds their documentation. */\n async scan() {\n Logger.verbose('Scanning plugin directory');\n // plugin directory doesn't exist\n if (!fs.existsSync(this.path)) {\n Logger.verbose('Plugin directory is missing');\n return false;\n }\n\n // make sure there are no plugins running\n if (this.plugins.some(p => p.isLoaded())) {\n Logger.errorp('Cannot re-scan plugins while a plugin is loaded');\n return false;\n }\n\n // find all directories in the plugin path\n const unsortedPlugins = (\n await Promise.all(\n fs\n .readdirSync(this.path)\n .map(dir => path.join(this.path, dir)) // convert from local paths\n .map(async dir => this.scanPlugin(dir)),\n )\n )\n // remove plugins without formats\n .filter(p => p);\n\n this.plugins = PluginLoader.calculateLoadOrder(unsortedPlugins);\n\n Logger.verbose('Finished scanning plugin directory');\n Logger.verbose(\n `Found ${this.plugins.length} plugins.Loading in this order: `,\n this.plugins.map(p => p.getName()),\n );\n\n this.buildDocumentation();\n\n return true;\n }\n\n // show helptext\n showHelp(player: string | Player, ...args: string[]) {\n // send the message to the player\n const send = (msg: string) => this.omegga.whisper(player, msg);\n\n // available commands and documentation from the plugin system\n const commands = this.commands ?? {};\n const docs = this.documentation ?? {};\n\n const splitHelper = (objs: string[]) => {\n const lines = [];\n while (objs.length > 0) {\n const [item] = objs.splice(0, 1);\n if (!lines.length || lines[lines.length - 1].length + item.length > 150)\n lines.push(item);\n else lines[lines.length - 1] += ', ' + item;\n }\n return lines;\n };\n\n // no arguments\n if (!args.length) {\n send(\n '\"Use <code>/plugins plugin</>, <code>/plugins !command</>, <code>/plugins /command</> for more information\"',\n );\n const plugins = Object.keys(docs).map(\n d =>\n `<color=\\\\\"${\n docs[d]._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa'\n }\\\\\">${d}</>`,\n );\n if (!plugins.length) {\n send('\"<b>No Installed Plugins</>\"');\n } else {\n const lines = splitHelper(plugins);\n send(`\"<b>Installed Plugins</>: ${lines[0]}\"`);\n for (let i = 1; i < lines.length; i++) send(`\"${lines[i]}\"`);\n }\n\n // plugin or command argument\n } else if (args.length > 0) {\n const target = args.join(' ').toLowerCase();\n\n // argument is a plugin; render description, author, and commands\n if (target in docs) {\n const doc = docs[target];\n const desc = doc.description || 'no description';\n const color = doc._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa';\n send(\n `\"<b>Plugin</> <code><color=\\\\\"${color}\\\\\">${doc.name}</></>: ${desc}\"`,\n );\n\n if (doc.author)\n send(`\"<b>Author</>: <color=\\\\\"c4d7f5\\\\\"><b>${doc.author}</></>\"`);\n\n if (doc.commands && doc.commands.length > 0) {\n const lines = splitHelper(\n doc.commands.map(c => `<code>${c.name}</>`),\n );\n send(`\"<b>Commands</>: ${lines[0]}\"`);\n for (let i = 1; i < lines.length; i++) send(`\"${lines[i]}\"`);\n }\n\n // argument is a command\n } else if (target in commands) {\n const cmd = commands[target];\n const desc = cmd.description || 'no description';\n const example = cmd.example || 'no example';\n const color = cmd._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa';\n send(\n `\"<b>Command</> <code><color=\\\\\"${color}\\\\\">${cmd.name}</></>: ${desc}\"`,\n );\n send(`\"<b>Example</>: <code>${example}</>\"`);\n if (cmd.args && cmd.args.length > 0) {\n send('\"<b>Arguments</>:\"');\n for (const arg of cmd.args) {\n const desc = arg.description || 'no description';\n send(\n `\"- <code>${arg.name}</>${\n arg.required ? ' (required)' : ''\n }: ${desc}\"`,\n );\n }\n } else {\n send('\"<b>Arguments</>: None\"');\n }\n\n // user takes the helptext literally\n } else if (\n args[0] === '!command' ||\n args[0] === '/command' ||\n args[0] === 'plugin'\n ) {\n send(\n '\"Use <code>/plugins [name of plugin]</> or <code>/plugins [name of !command or /command]</> for more help for the respective plugin or command\"',\n );\n\n // argument is not found\n } else {\n send('\"Could not find that command or plugin\"');\n }\n\n // too many arguments\n } else {\n send(\n '\"Use <code>/plugins</> to list plugins and <code>/plugins plugin</> or <code>!help !command or /command</> for more information\"',\n );\n }\n }\n\n // generate documentation from loaded plugins\n buildDocumentation() {\n this.documentation = {};\n this.commands = {};\n for (const plugin of this.plugins) {\n // make sure this plugin has docs\n const doc = JSON.parse(JSON.stringify(plugin.getDocumentation()));\n if (!doc) continue;\n const name = plugin.getName();\n if (!name) continue;\n\n // add the documentation into a dictionary\n this.documentation[name.toLowerCase()] = doc;\n doc._plugin = plugin;\n\n // add all the commands into a dictionary\n if (doc.commands) {\n for (const cmd of doc.commands) {\n this.commands[cmd.name.toLowerCase()] = cmd;\n cmd._plugin = plugin;\n }\n }\n }\n }\n}\n"],"names":["soft","Logger","RpcPlugin","NodeVmPlugin","NodePlugin","desc"],"mappings":";;;;;;;;;;AA0CO,MAAM,cAAc;AAAA,EAKzB,YAAY,OAAkB,QAAgB;AAC5C,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,OAAO,OAAO,QAAA;AAAA,EACrB;AAAA;AAAA,EAGA,mBAAmB;AACjB,UAAM,MAAM,KAAK,OAAO,iBAAA;AAExB,QAAI,CAAC,IAAK,QAAO,CAAA;AACjB,QAAI,CAAC,IAAI,OAAQ,QAAO,CAAA;AAGxB,UAAM,SAAkC,CAAA;AACxC,eAAW,KAAK,IAAI,QAAQ;AAC1B,aAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,UAAU,OAAgB;AAC9B,UAAM,KAAK,MAAM;AAAA,MACf,EAAE,MAAM,UAAU,QAAQ,KAAK,KAAA;AAAA,MAC/B;AAAA,QACE,MAAM,EAAE,MAAA;AAAA,MAAM;AAAA,MAEhB,EAAE,QAAQ,KAAA;AAAA,IAAK;AAAA,EAEnB;AAAA;AAAA,EAGA,MAAM,YAA8C;AAClD,UAAM,SAAS,MAAM,KAAK,MAAM,QAAsB;AAAA,MACpD,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,IAAA,CACd;AACD,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA,EAGA,MAAM,aAAa;AACjB,UAAM,KAAK,MAAM;AAAA,MACf,EAAE,MAAM,UAAU,QAAQ,KAAK,KAAA;AAAA,MAC/B,EAAE,OAAO,KAAA;AAAA,IAAK;AAAA,EAElB;AAAA;AAAA,EAGA,MAAM,OAAO;AAEX,UAAM,cAAc,KAAK,iBAAA;AACzB,UAAM,SAAS,MAAM,KAAK,UAAA;AAE1B,UAAM,KAAK,UAAU;AAAA;AAAA,MAEnB,GAAG;AAAA;AAAA,MAGH,GAAI,SAAS,SAAS,CAAA;AAAA,IAAC,CACxB;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,IAAO,KAAa;AACxB,QAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG;AACjD,UAAM,MAAM,MAAM,KAAK,MAAM,QAAuB;AAAA,MAClD,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,OAAO,KAAa;AACxB,QAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG;AACjD,UAAM,KAAK,MAAM,OAAO,EAAE,MAAM,SAAS,QAAQ,KAAK,MAAM,IAAA,GAAO,CAAA,CAAE;AAAA,EACvE;AAAA;AAAA,EAGA,MAAM,OAAO;AACX,UAAM,KAAK,MAAM;AAAA,MACf,EAAE,MAAM,SAAS,QAAQ,KAAK,KAAA;AAAA,MAC9B,EAAE,OAAO,KAAA;AAAA,IAAK;AAAA,EAElB;AAAA;AAAA,EAGA,MAAM,QAAQ;AACZ,WAAO,MAAM,KAAK,MAAM,MAAM,EAAE,MAAM,SAAS,QAAQ,KAAK,MAAM;AAAA,EACpE;AAAA;AAAA,EAGA,MAAM,OAAO;AACX,UAAM,UAAU,MAAM,KAAK,MAAM,KAAiB;AAAA,MAChD,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,IAAA,CACd;AACD,WAAO,QAAQ,IAAI,CAAA,MAAK,EAAE,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,MAAM,IAAiB,KAAa,OAAU;AAC5C,QAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG;AACjD,UAAM,KAAK,MAAM;AAAA,MACf,EAAE,MAAM,SAAS,QAAQ,KAAK,MAAM,IAAA;AAAA,MACpC,EAAE,MAAM,EAAE,QAAM;AAAA,MAChB,EAAE,QAAQ,KAAA;AAAA,IAAK;AAAA,EAEnB;AACF;AAMO,MAAM,aAAa;AAAA,EAUxB,YAAY,SAAiB,QAAiB;AAC5C,SAAK,OAAO,KAAK,KAAK,SAASA,WAAAA,QAAK,WAAW;AAC/C,SAAK,SAAS;AACd,SAAK,QAAQ,UAAU,OAAO;AAAA,MAC5B,UAAU,KAAK,KAAK,SAASA,WAAAA,QAAK,WAAWA,WAAAA,QAAK,YAAY;AAAA,MAC9D,UAAU;AAAA,IAAA,CACX;AACD,SAAK,MAAM,YAAY,0BAA0B,MAAO,KAAK,CAAC;AAC9D,SAAK,UAAU,CAAA;AAEfC,WAAAA,QAAO,QAAQ,wBAAwB;AACvC,SAAK,UAAU,CAACC,8BAAWC,iBAAAA,SAAcC,mBAAAA,OAAU;AACnDH,WAAAA,QAAO,QAAQ,yBAAyB,KAAK,OAAO;AAEpD,QAAI,QAAQ;AAEV,aAAO,GAAG,gBAAgB,KAAK,SAAS,KAAK,IAAI,CAAC;AAGlD,aAAO,GAAG,eAAe,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,IACnD;AAAA,EACF;AAAA;AAAA,EAGA,UAAU,KAAa;AACrB,WAAO,QAAQ,aAAa,KAAK,QAAQ,KAAK,CAAA,MAAK,EAAE,UAAU,GAAG,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,SAAS;AACb,QAAI,KAAK;AACTA,WAAAA,QAAO,QAAQ,mBAAmB;AAClC,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI;AAEF,YAAI,EAAE,SAAA,EAAY,OAAM,EAAE,OAAA;AAG1B,YAAI,CAAC,EAAE,YAAY;AAEjB,cAAI,EAAE,aAAa;AACjBA,mBAAAA,QAAO;AAAA,cACL;AAAA,cACC,EAAE,YAA8B,UAAA;AAAA,cACjC,EAAE,UAAU;AAAA,YAAA;AAEd,iBAAM,MAAM,EAAE,KAAA,KAAW;AAAA,UAC3B;AAAA,QACF,OAAO;AACLA,iBAAAA,QAAO;AAAA,YACL;AAAA,YACA,EAAE,QAAA,EAAU,UAAU;AAAA,UAAA;AAExB,eAAK;AAAA,QACP;AAAA,MACF,SAAS,KAAK;AACZA,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,EAAE,UAAU,UAAU;AAAA,UACtB;AAAA,QAAA;AAEF,aAAK;AAAA,MACP;AAAA,IACF;AACAA,WAAAA,QAAO,QAAQ,4BAA4B;AAC3C,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,SAAS;AACbA,WAAAA,QAAO,QAAQ,mBAAmB;AAClC,QAAI,KAAK;AAET,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI;AAEF,YAAI,EAAE,YAAY;AAChB,cAAI,CAAE,MAAM,EAAE,UAAW;AACvBA,mBAAAA,QAAO;AAAA,cACL;AAAA,cACA,EAAE,QAAA,EAAU,UAAU;AAAA,YAAA;AAExB,iBAAK;AACL;AAAA,UACF,OAAO;AACLA,mBAAAA,QAAO,QAAQ,YAAY,EAAE,QAAA,EAAU,SAAS;AAAA,UAClD;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACVA,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,EAAE,QAAA,EAAU,UAAU;AAAA,QAAA;AAExB,aAAK;AAAA,MACP;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,WAAW,KAA0C;AACzDA,WAAAA,QAAO,QAAQ,mBAAmB,IAAI,SAAS;AAE/C,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvBA,aAAAA,QAAO,OAAO,mCAAmC,IAAI,UAAU,SAAS;AACxE;AAAA,IACF;AACA,QAAI,CAAC,GAAG,SAAS,GAAG,EAAE,eAAe;AACnCA,aAAAA,QAAO,OAAO,8BAA8B,IAAI,UAAU,SAAS;AACnE;AAAA,IACF;AAGA,UAAM,eAAe,KAAK,QAAQ,KAAK,OAAK,EAAE,QAAQ,GAAG,CAAC;AAG1D,QAAI,CAAC,cAAc;AACjBA,qBAAO,OAAO,6BAA6B,GAAG;AAC9C;AAAA,IACF;AACA,QAAI;AAEF,YAAM,SAAS,gBAAgB,IAAI,aAAa,KAAK,KAAK,MAAM;AAChE,UAAI,CAAC,OAAO,oBAAoB;AAC9BA,uBAAO,OAAO,4CAA4C,GAAG;AAC7D;AAAA,MACF;AAGA,YAAM,UAAU,IAAI,cAAc,KAAK,OAAO,MAAM;AACpD,YAAM,QAAQ,KAAA;AAGd,aAAO,WAAW,OAAO;AAGzB,aAAO;AAAA,IACT,SAAS,GAAG;AAEVA,aAAAA,QAAO;AAAA,QACL;AAAA,QACA,IAAI,UAAU;AAAA,QACd;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,OAAO,mBAAmB,iBAA2B;AACnD,QAAI,CAAC,gBAAgB,QAAQ;AAC3B,aAAO,CAAA;AAAA,IACT;AAUA,UAAM,WAAW,gBAAgB,OAAO,CAAC,OAAO,WAAW;AACzD,YAAM,OAAO,OAAO,QAAA;AACpB,YAAM;AAAA,QACJ,eAAe,CAAA;AAAA,QACf,YAAY,CAAA;AAAA,QACZ,aAAa,CAAA;AAAA,MAAC,IACZ,OAAO,gBAAgB,CAAA;AAG3B,YAAM,IAAI,MAAM,CAAA;AAChB,YAAM,IAAI,EAAE,WAAW;AACvB,YAAM,IAAI,EAAE,WAAW,CAAA;AACvB,YAAM,IAAI,EAAE,aAAa;AAGzB,iBAAW,WAAW,CAAC,GAAG,WAAW,GAAG,OAAO,KAAK,YAAY,CAAC,GAAG;AAClE,cAAM,IAAI,EAAE,OAAO,KAAK,OAAO;AAG/B,cAAM,OAAO,MAAM,CAAA;AACnB,cAAM,OAAO,EAAE,aAAa;AAE5B,cAAM,OAAO,EAAE;AAAA,MACjB;AAGA,iBAAW,WAAW,CAAC,GAAG,UAAU,GAAG;AAErC,cAAM,OAAO,MAAM,CAAA;AACnB,cAAM,OAAO,EAAE,WAAW,CAAA;AAE1B,cAAM,OAAO,EAAE,OAAO,KAAK,IAAI;AAE/B,cAAM,IAAI,EAAE;AAAA,MACd;AAEA,aAAO;AAAA,IACT,GAAG,CAAA,CAAqB;AAGxB,QAAI,QAAkB,OAAO,QAAQ,QAAQ,EAAE;AAAA,MAC7C,CAAC,KAAK,CAAC,MAAM,EAAE,SAAA,CAAU,MAAM;AAC7B,YAAI,aAAa,GAAG;AAClB,cAAI,KAAK,IAAI;AAAA,QACf;AACA,eAAO;AAAA,MACT;AAAA,MACA,CAAA;AAAA,IAAC;AAIH,UAAM,SAAqB,CAAA;AAC3B,WAAO,MAAM,QAAQ;AAEnB,aAAO,QAAQ,CAAC,GAAG,KAAK,CAAC;AAEzB,YAAM,WAAW,CAAA;AAEjB,iBAAW,QAAQ,OAAO;AACxB,mBAAW,YAAY,SAAS,IAAI,EAAE,QAAQ;AAC5C,mBAAS,QAAQ,EAAE;AACnB,cAAI,SAAS,QAAQ,EAAE,aAAa,GAAG;AACrC,qBAAS,KAAK,QAAQ;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,cAAQ;AAAA,IACV;AAEA,QAAI,OAAO,OAAO,QAAQ,EAAE,KAAK,CAAC,EAAE,SAAA,MAAe,WAAW,CAAC,GAAG;AAChEA,aAAAA,QAAO;AAAA,QACL;AAAA,MAAA;AAEF,aAAO,CAAA;AAAA,IACT;AAEA,WAAO,OACJ;AAAA,MAAI,CAAA,2BACH,uBACG,IAAI,CAAA,SAAQ,SAAS,IAAI,EAAE,MAAM,EAEjC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAA,EAAU,cAAc,EAAE,SAAS,CAAC;AAAA,IAAA,EAEzD,KAAA,EACA,KAAK,CAAC,GAAG,MAAM;AACd,YAAM,IAAI,EAAE,cAAc;AAC1B,YAAM,IAAI,EAAE,cAAc;AAC1B,YAAM,KAAK,KAAK;AAChB,YAAM,KAAK,KAAK;AAIhB,UAAI,MAAM,GAAI,QAAO;AACrB,UAAI,GAAI,QAAO,KAAK,IAAI,IAAI;AAC5B,UAAI,GAAI,QAAO,KAAK,IAAI,KAAK;AAC7B,aAAO,IAAI;AAAA,IACb,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,MAAM,OAAO;AACXA,WAAAA,QAAO,QAAQ,2BAA2B;AAE1C,QAAI,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG;AAC7BA,aAAAA,QAAO,QAAQ,6BAA6B;AAC5C,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,QAAQ,KAAK,OAAK,EAAE,SAAA,CAAU,GAAG;AACxCA,aAAAA,QAAO,OAAO,iDAAiD;AAC/D,aAAO;AAAA,IACT;AAGA,UAAM,mBACJ,MAAM,QAAQ;AAAA,MACZ,GACG,YAAY,KAAK,IAAI,EACrB,IAAI,CAAA,QAAO,KAAK,KAAK,KAAK,MAAM,GAAG,CAAC,EACpC,IAAI,OAAM,QAAO,KAAK,WAAW,GAAG,CAAC;AAAA,IAAA,GAIzC,OAAO,CAAA,MAAK,CAAC;AAEhB,SAAK,UAAU,aAAa,mBAAmB,eAAe;AAE9DA,WAAAA,QAAO,QAAQ,oCAAoC;AACnDA,WAAAA,QAAO;AAAA,MACL,SAAS,KAAK,QAAQ,MAAM;AAAA,MAC5B,KAAK,QAAQ,IAAI,CAAA,MAAK,EAAE,SAAS;AAAA,IAAA;AAGnC,SAAK,mBAAA;AAEL,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,SAAS,WAA4B,MAAgB;AAEnD,UAAM,OAAO,CAAC,QAAgB,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAG7D,UAAM,WAAW,KAAK,YAAY,CAAA;AAClC,UAAM,OAAO,KAAK,iBAAiB,CAAA;AAEnC,UAAM,cAAc,CAAC,SAAmB;AACtC,YAAM,QAAQ,CAAA;AACd,aAAO,KAAK,SAAS,GAAG;AACtB,cAAM,CAAC,IAAI,IAAI,KAAK,OAAO,GAAG,CAAC;AAC/B,YAAI,CAAC,MAAM,UAAU,MAAM,MAAM,SAAS,CAAC,EAAE,SAAS,KAAK,SAAS;AAClE,gBAAM,KAAK,IAAI;AAAA,YACZ,OAAM,MAAM,SAAS,CAAC,KAAK,OAAO;AAAA,MACzC;AACA,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,QACE;AAAA,MAAA;AAEF,YAAM,UAAU,OAAO,KAAK,IAAI,EAAE;AAAA,QAChC,CAAA,MACE,aACE,KAAK,CAAC,EAAE,QAAQ,SAAA,IAAa,WAAW,QAC1C,OAAO,CAAC;AAAA,MAAA;AAEZ,UAAI,CAAC,QAAQ,QAAQ;AACnB,aAAK,8BAA8B;AAAA,MACrC,OAAO;AACL,cAAM,QAAQ,YAAY,OAAO;AACjC,aAAK,6BAA6B,MAAM,CAAC,CAAC,GAAG;AAC7C,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,MAAK,IAAI,MAAM,CAAC,CAAC,GAAG;AAAA,MAC7D;AAAA,IAGF,WAAW,KAAK,SAAS,GAAG;AAC1B,YAAM,SAAS,KAAK,KAAK,GAAG,EAAE,YAAA;AAG9B,UAAI,UAAU,MAAM;AAClB,cAAM,MAAM,KAAK,MAAM;AACvB,cAAM,OAAO,IAAI,eAAe;AAChC,cAAM,QAAQ,IAAI,QAAQ,SAAA,IAAa,WAAW;AAClD;AAAA,UACE,iCAAiC,KAAK,OAAO,IAAI,IAAI,WAAW,IAAI;AAAA,QAAA;AAGtE,YAAI,IAAI;AACN,eAAK,yCAAyC,IAAI,MAAM,SAAS;AAEnE,YAAI,IAAI,YAAY,IAAI,SAAS,SAAS,GAAG;AAC3C,gBAAM,QAAQ;AAAA,YACZ,IAAI,SAAS,IAAI,OAAK,SAAS,EAAE,IAAI,KAAK;AAAA,UAAA;AAE5C,eAAK,oBAAoB,MAAM,CAAC,CAAC,GAAG;AACpC,mBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,MAAK,IAAI,MAAM,CAAC,CAAC,GAAG;AAAA,QAC7D;AAAA,MAGF,WAAW,UAAU,UAAU;AAC7B,cAAM,MAAM,SAAS,MAAM;AAC3B,cAAM,OAAO,IAAI,eAAe;AAChC,cAAM,UAAU,IAAI,WAAW;AAC/B,cAAM,QAAQ,IAAI,QAAQ,SAAA,IAAa,WAAW;AAClD;AAAA,UACE,kCAAkC,KAAK,OAAO,IAAI,IAAI,WAAW,IAAI;AAAA,QAAA;AAEvE,aAAK,yBAAyB,OAAO,MAAM;AAC3C,YAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,GAAG;AACnC,eAAK,oBAAoB;AACzB,qBAAW,OAAO,IAAI,MAAM;AAC1B,kBAAMI,QAAO,IAAI,eAAe;AAChC;AAAA,cACE,YAAY,IAAI,IAAI,MAClB,IAAI,WAAW,gBAAgB,EACjC,KAAKA,KAAI;AAAA,YAAA;AAAA,UAEb;AAAA,QACF,OAAO;AACL,eAAK,yBAAyB;AAAA,QAChC;AAAA,MAGF,WACE,KAAK,CAAC,MAAM,cACZ,KAAK,CAAC,MAAM,cACZ,KAAK,CAAC,MAAM,UACZ;AACA;AAAA,UACE;AAAA,QAAA;AAAA,MAIJ,OAAO;AACL,aAAK,yCAAyC;AAAA,MAChD;AAAA,IAGF,OAAO;AACL;AAAA,QACE;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA;AAAA,EAGA,qBAAqB;AACnB,SAAK,gBAAgB,CAAA;AACrB,SAAK,WAAW,CAAA;AAChB,eAAW,UAAU,KAAK,SAAS;AAEjC,YAAM,MAAM,KAAK,MAAM,KAAK,UAAU,OAAO,iBAAA,CAAkB,CAAC;AAChE,UAAI,CAAC,IAAK;AACV,YAAM,OAAO,OAAO,QAAA;AACpB,UAAI,CAAC,KAAM;AAGX,WAAK,cAAc,KAAK,YAAA,CAAa,IAAI;AACzC,UAAI,UAAU;AAGd,UAAI,IAAI,UAAU;AAChB,mBAAW,OAAO,IAAI,UAAU;AAC9B,eAAK,SAAS,IAAI,KAAK,YAAA,CAAa,IAAI;AACxC,cAAI,UAAU;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../../src/omegga/plugin.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { IPluginCommand, IPluginDocumentation } from '@/plugin';\nimport soft from '@/softconfig';\nimport { openDb } from '@/db/connection';\nimport { runPluginMigrations } from '@/db/migrate';\nimport { importNedbIfNeeded } from '@/db/nedbImport';\nimport * as pluginSchema from '@/db/pluginSchema';\nimport { and, count, eq } from 'drizzle-orm';\nimport {\n drizzle,\n type BetterSQLite3Database,\n} from 'drizzle-orm/better-sqlite3';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type Player from './player';\nimport { Plugin } from './plugin/interface';\nimport RpcPlugin from './plugin/plugin_jsonrpc_stdio';\nimport NodeVmPlugin from './plugin/plugin_node_safe';\nimport NodePlugin from './plugin/plugin_node_unsafe';\nimport type Omegga from './server';\n\nexport interface IPluginJSON {\n formatVersion: number;\n omeggaVersion: string;\n emitConfig?: string;\n dependencies?: {\n [pluginName: string]: { optional?: boolean; repo?: string } | string;\n };\n loadPriority?: number;\n loadBefore?: string[];\n loadAfter?: string[];\n}\n\n// TODO: move doc.json to this file (maybe)\n// TODO: cleaner plugin error messages\n\n// key-value storage for a plugin\nexport class PluginStorage {\n private db: BetterSQLite3Database;\n plugin: Plugin;\n name: string;\n\n constructor(db: BetterSQLite3Database, plugin: Plugin) {\n this.db = db;\n this.plugin = plugin;\n this.name = plugin.getName();\n }\n\n // get the initial config values\n getDefaultConfig() {\n const doc = this.plugin.getDocumentation();\n if (!doc) return {};\n if (!doc.config) return {};\n const config: Record<string, unknown> = {};\n for (const k in doc.config) {\n config[k] = doc.config[k].default;\n }\n return config;\n }\n\n // set the config field for this plugin\n async setConfig(value: unknown) {\n this.db\n .insert(pluginSchema.pluginConfig)\n .values({ plugin: this.name, value: value as Record<string, unknown> })\n .onConflictDoUpdate({\n target: pluginSchema.pluginConfig.plugin,\n set: { value: value as Record<string, unknown> },\n })\n .run();\n }\n\n // get the config for this plugin\n async getConfig(): Promise<Record<string, unknown>> {\n const row = this.db\n .select()\n .from(pluginSchema.pluginConfig)\n .where(eq(pluginSchema.pluginConfig.plugin, this.name))\n .get();\n if (!row) return null;\n return row.value;\n }\n\n /** Wipes the configs for the plugin. */\n async wipeConfig() {\n this.db\n .delete(pluginSchema.pluginConfig)\n .where(eq(pluginSchema.pluginConfig.plugin, this.name))\n .run();\n }\n\n /** Initializes the configs for the plugin into the database and sets default configs. */\n async init() {\n const defaultConf = this.getDefaultConfig();\n const config = await this.getConfig();\n await this.setConfig({\n // use default config\n ...defaultConf,\n // override with actual config values\n ...(config ? config : {}),\n });\n }\n\n // get a stored object\n async get<T>(key: string) {\n if (typeof key !== 'string' || key.length === 0) return;\n const row = this.db\n .select()\n .from(pluginSchema.pluginStore)\n .where(\n and(\n eq(pluginSchema.pluginStore.plugin, this.name),\n eq(pluginSchema.pluginStore.key, key),\n ),\n )\n .get();\n if (!row) return null;\n return row.value as T;\n }\n\n // delete a stored object\n async delete(key: string) {\n if (typeof key !== 'string' || key.length === 0) return;\n this.db\n .delete(pluginSchema.pluginStore)\n .where(\n and(\n eq(pluginSchema.pluginStore.plugin, this.name),\n eq(pluginSchema.pluginStore.key, key),\n ),\n )\n .run();\n }\n\n // clear all stored values\n async wipe() {\n this.db\n .delete(pluginSchema.pluginStore)\n .where(eq(pluginSchema.pluginStore.plugin, this.name))\n .run();\n }\n\n // count number of objects in store\n async count() {\n const result = this.db\n .select({ count: count() })\n .from(pluginSchema.pluginStore)\n .where(eq(pluginSchema.pluginStore.plugin, this.name))\n .get();\n return result?.count ?? 0;\n }\n\n // get keys of all objects in store\n async keys() {\n const rows = this.db\n .select({ key: pluginSchema.pluginStore.key })\n .from(pluginSchema.pluginStore)\n .where(eq(pluginSchema.pluginStore.plugin, this.name))\n .all();\n return rows.map(r => r.key);\n }\n\n // set a stored value\n async set<T = unknown>(key: string, value: T) {\n if (typeof key !== 'string' || key.length === 0) return;\n // the value column is NOT NULL and drizzle's json mode passes JS null/\n // undefined through as SQL NULL (and values that serialize to undefined,\n // e.g. functions); treat all of these as a delete, matching get()'s null\n // return for absent keys (NeDB silently stored such values)\n if (value == null || JSON.stringify(value) === undefined)\n return this.delete(key);\n this.db\n .insert(pluginSchema.pluginStore)\n .values({ plugin: this.name, key, value })\n .onConflictDoUpdate({\n target: [pluginSchema.pluginStore.plugin, pluginSchema.pluginStore.key],\n set: { value },\n })\n .run();\n }\n}\n\n/*\n Plugin Loader and Scanner\n Allows omegga to scan in plugins and load them\n*/\nexport class PluginLoader {\n path: string;\n dataPath: string;\n omegga: Omegga;\n pluginDb: BetterSQLite3Database;\n formats: (typeof Plugin)[];\n plugins: Plugin[];\n\n commands: Record<string, IPluginCommand & { _plugin: Plugin }>;\n documentation: Record<string, IPluginDocumentation & { _plugin: Plugin }>;\n\n constructor(workDir: string, omegga?: Omegga) {\n this.path = path.join(workDir, soft.PLUGIN_PATH);\n this.omegga = omegga;\n\n this.dataPath = path.join(workDir, soft.DATA_PATH);\n const sqlite = openDb(path.join(this.dataPath, soft.PLUGINS_DB));\n this.pluginDb = drizzle(sqlite);\n runPluginMigrations(this.pluginDb);\n\n this.plugins = [];\n\n Logger.verbose('Loading plugin formats');\n this.formats = [RpcPlugin, NodeVmPlugin, NodePlugin];\n Logger.verbose('Found plugin formats:', this.formats);\n\n if (omegga) {\n // soon to be deprecated !help\n omegga.on('chatcmd:help', this.showHelp.bind(this));\n\n // use /plugins to get help\n omegga.on('cmd:plugins', this.showHelp.bind(this));\n }\n }\n\n // determine if this command is a command on the plugin\n isCommand(cmd: string) {\n return cmd === 'plugins' || this.plugins.some(p => p.isCommand(cmd));\n }\n\n // unload and load all installed plugins\n async reload() {\n let ok = true;\n Logger.verbose('Reloading plugins');\n for (const p of this.plugins) {\n try {\n // unload the plugin if it's loaded\n if (p.isLoaded()) await p.unload();\n\n // load the plugin if it successfully unloaded\n if (!p.isLoaded()) {\n // only load it if it is enabled\n if (p.isEnabled()) {\n Logger.verbose(\n 'Loading plugin',\n (p.constructor as typeof Plugin).getFormat(),\n p.getName().underline,\n );\n ok = (await p.load()) || ok;\n }\n } else {\n Logger.errorp(\n 'Did not successfully unload plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n }\n } catch (err) {\n Logger.errorp(\n 'Error loading plugin',\n p.getName().brightRed.underline,\n err,\n );\n ok = false;\n }\n }\n Logger.verbose('Finished reloading plugins');\n return ok;\n }\n\n // stop all plugins from running\n async unload() {\n Logger.verbose('Unloading plugins');\n let ok = true;\n // potentually use Promise.all\n for (const p of this.plugins) {\n try {\n // unload the plugin if it's loaded\n if (p.isLoaded()) {\n if (!(await p.unload())) {\n Logger.errorp(\n 'Could not unloading plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n continue;\n } else {\n Logger.verbose('Unloaded', p.getName().underline);\n }\n }\n } catch (e) {\n Logger.errorp(\n 'Error unloading plugin',\n p.getName().brightRed.underline,\n );\n ok = false;\n }\n }\n return ok;\n }\n\n /** Scans a plugin at the specified directory and create a Plugin object. */\n async scanPlugin(dir: string): Promise<Plugin | undefined> {\n Logger.verbose('Scanning plugin', dir.underline);\n\n // legacy NeDB plugin data must be imported before storage.init() writes\n // defaults, even when the webserver (the other import trigger) is disabled\n await importNedbIfNeeded(this.dataPath);\n\n if (!fs.existsSync(dir)) {\n Logger.errorp('Plugin directory does not exist', dir.brightRed.underline);\n return;\n }\n if (!fs.statSync(dir).isDirectory()) {\n Logger.errorp('Plugin must be a directory', dir.brightRed.underline);\n return;\n }\n\n // find a plugin format that can load in this plugin\n const PluginFormat = this.formats.find(f => f.canLoad(dir));\n\n // let users know if there's a missing plugin format\n if (!PluginFormat) {\n Logger.errorp('Missing plugin format for', dir);\n return;\n }\n try {\n // create the plugin format\n const plugin = PluginFormat && new PluginFormat(dir, this.omegga);\n if (!plugin.getDocumentation()) {\n Logger.errorp('Missing/invalid plugin documentation for', dir);\n return;\n }\n\n // create its storage\n const storage = new PluginStorage(this.pluginDb, plugin);\n await storage.init();\n\n // load the storage in\n plugin.setStorage(storage);\n\n // if there is a plugin format, create the plugin instance (but don't load yet)\n return plugin;\n } catch (e) {\n // if a plugin format fails to load, prevent omegga from dying\n Logger.errorp(\n 'Error loading plugin',\n dir.brightRed.underline,\n PluginFormat,\n e,\n );\n }\n }\n\n static calculateLoadOrder(unsortedPlugins: Plugin[]) {\n if (!unsortedPlugins.length) {\n return [];\n }\n\n // create a DAG of plugins based on dependencies, loadBefore, and loadAfter\n type DependencyGraphNode = {\n plugin: Plugin;\n inDegree: number;\n behind: string[];\n };\n type DependencyGraph = Record<string, DependencyGraphNode>;\n\n const depGraph = unsortedPlugins.reduce((graph, plugin) => {\n const name = plugin.getName();\n const {\n dependencies = {},\n loadAfter = [],\n loadBefore = [],\n } = plugin.pluginConfig ?? {};\n\n //create a node in case it doesn't exist\n graph[name] ??= {} as DependencyGraphNode;\n graph[name].plugin ??= plugin;\n graph[name].behind ??= [];\n graph[name].inDegree ??= 0;\n\n // add edges for everything ahead of name\n for (const depName of [...loadAfter, ...Object.keys(dependencies)]) {\n graph[name].behind.push(depName);\n\n //create a node and set an inDegree in case it doesn't exist\n graph[depName] ??= {} as DependencyGraphNode;\n graph[depName].inDegree ??= 0;\n\n graph[depName].inDegree++;\n }\n\n // add edges for everything behind name\n for (const depName of [...loadBefore]) {\n //create a node and a behind array in case it doesn't exist\n graph[depName] ??= {} as DependencyGraphNode;\n graph[depName].behind ??= [];\n\n graph[depName].behind.push(name);\n\n graph[name].inDegree++;\n }\n\n return graph;\n }, {} as DependencyGraph);\n\n // find all nodes with no incoming edges to start processing\n let zeros: string[] = Object.entries(depGraph).reduce(\n (arr, [name, { inDegree }]) => {\n if (inDegree === 0) {\n arr.push(name);\n }\n return arr;\n },\n [],\n );\n\n // execute a topological sort, grouping nodes where the order doesn't matter, in the order that *does* matter\n const result: string[][] = [];\n while (zeros.length) {\n // these nodes can be in any order\n result.unshift([...zeros]);\n\n const nextZero = [];\n\n for (const node of zeros) {\n for (const neighbor of depGraph[node].behind) {\n depGraph[neighbor].inDegree--;\n if (depGraph[neighbor].inDegree === 0) {\n nextZero.push(neighbor);\n }\n }\n }\n\n zeros = nextZero;\n }\n\n if (Object.values(depGraph).some(({ inDegree }) => inDegree > 0)) {\n Logger.verbose(\n 'Cyclic or missing dependencies detected in plugins. Could not resolve load order.',\n );\n return [];\n }\n\n return result\n .map(interchangeablePlugins =>\n interchangeablePlugins\n .map(name => depGraph[name].plugin)\n // alphabetical order for the sub groups where order doesn't matter so its deterministic\n .sort((a, b) => a.getName().localeCompare(b.getName())),\n )\n .flat()\n .sort((a, b) => {\n const A = a.pluginConfig?.loadPriority;\n const B = b.pluginConfig?.loadPriority;\n const aU = A == null;\n const bU = B == null;\n // sort the result by load priority so that\n // a defined loadPriority that is negative comes before undefined (lower is earlier)\n // and a defined loadPriority that is positive comes after undefined (higher is later)\n if (aU && bU) return 0;\n if (aU) return B <= 0 ? 1 : -1;\n if (bU) return A <= 0 ? -1 : 1;\n return A - B;\n });\n }\n\n /** Scans plugin directory for plugins and builds their documentation. */\n async scan() {\n Logger.verbose('Scanning plugin directory');\n // plugin directory doesn't exist\n if (!fs.existsSync(this.path)) {\n Logger.verbose('Plugin directory is missing');\n return false;\n }\n\n // make sure there are no plugins running\n if (this.plugins.some(p => p.isLoaded())) {\n Logger.errorp('Cannot re-scan plugins while a plugin is loaded');\n return false;\n }\n\n // find all directories in the plugin path\n const unsortedPlugins = (\n await Promise.all(\n fs\n .readdirSync(this.path)\n .map(dir => path.join(this.path, dir)) // convert from local paths\n .map(async dir => this.scanPlugin(dir)),\n )\n )\n // remove plugins without formats\n .filter(p => p);\n\n this.plugins = PluginLoader.calculateLoadOrder(unsortedPlugins);\n\n Logger.verbose('Finished scanning plugin directory');\n Logger.verbose(\n `Found ${this.plugins.length} plugins.Loading in this order: `,\n this.plugins.map(p => p.getName()),\n );\n\n this.buildDocumentation();\n\n return true;\n }\n\n // show helptext\n showHelp(player: string | Player, ...args: string[]) {\n // send the message to the player\n const send = (msg: string) => this.omegga.whisper(player, msg);\n\n // available commands and documentation from the plugin system\n const commands = this.commands ?? {};\n const docs = this.documentation ?? {};\n\n const splitHelper = (objs: string[]) => {\n const lines = [];\n while (objs.length > 0) {\n const [item] = objs.splice(0, 1);\n if (!lines.length || lines[lines.length - 1].length + item.length > 150)\n lines.push(item);\n else lines[lines.length - 1] += ', ' + item;\n }\n return lines;\n };\n\n // no arguments\n if (!args.length) {\n send(\n '\"Use <code>/plugins plugin</>, <code>/plugins !command</>, <code>/plugins /command</> for more information\"',\n );\n const plugins = Object.keys(docs).map(\n d =>\n `<color=\\\\\"${\n docs[d]._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa'\n }\\\\\">${d}</>`,\n );\n if (!plugins.length) {\n send('\"<b>No Installed Plugins</>\"');\n } else {\n const lines = splitHelper(plugins);\n send(`\"<b>Installed Plugins</>: ${lines[0]}\"`);\n for (let i = 1; i < lines.length; i++) send(`\"${lines[i]}\"`);\n }\n\n // plugin or command argument\n } else if (args.length > 0) {\n const target = args.join(' ').toLowerCase();\n\n // argument is a plugin; render description, author, and commands\n if (target in docs) {\n const doc = docs[target];\n const desc = doc.description || 'no description';\n const color = doc._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa';\n send(\n `\"<b>Plugin</> <code><color=\\\\\"${color}\\\\\">${doc.name}</></>: ${desc}\"`,\n );\n\n if (doc.author)\n send(`\"<b>Author</>: <color=\\\\\"c4d7f5\\\\\"><b>${doc.author}</></>\"`);\n\n if (doc.commands && doc.commands.length > 0) {\n const lines = splitHelper(\n doc.commands.map(c => `<code>${c.name}</>`),\n );\n send(`\"<b>Commands</>: ${lines[0]}\"`);\n for (let i = 1; i < lines.length; i++) send(`\"${lines[i]}\"`);\n }\n\n // argument is a command\n } else if (target in commands) {\n const cmd = commands[target];\n const desc = cmd.description || 'no description';\n const example = cmd.example || 'no example';\n const color = cmd._plugin.isLoaded() ? 'aaffaa' : 'aaaaaa';\n send(\n `\"<b>Command</> <code><color=\\\\\"${color}\\\\\">${cmd.name}</></>: ${desc}\"`,\n );\n send(`\"<b>Example</>: <code>${example}</>\"`);\n if (cmd.args && cmd.args.length > 0) {\n send('\"<b>Arguments</>:\"');\n for (const arg of cmd.args) {\n const desc = arg.description || 'no description';\n send(\n `\"- <code>${arg.name}</>${\n arg.required ? ' (required)' : ''\n }: ${desc}\"`,\n );\n }\n } else {\n send('\"<b>Arguments</>: None\"');\n }\n\n // user takes the helptext literally\n } else if (\n args[0] === '!command' ||\n args[0] === '/command' ||\n args[0] === 'plugin'\n ) {\n send(\n '\"Use <code>/plugins [name of plugin]</> or <code>/plugins [name of !command or /command]</> for more help for the respective plugin or command\"',\n );\n\n // argument is not found\n } else {\n send('\"Could not find that command or plugin\"');\n }\n\n // too many arguments\n } else {\n send(\n '\"Use <code>/plugins</> to list plugins and <code>/plugins plugin</> or <code>!help !command or /command</> for more information\"',\n );\n }\n }\n\n // generate documentation from loaded plugins\n buildDocumentation() {\n this.documentation = {};\n this.commands = {};\n for (const plugin of this.plugins) {\n // make sure this plugin has docs\n const doc = JSON.parse(JSON.stringify(plugin.getDocumentation()));\n if (!doc) continue;\n const name = plugin.getName();\n if (!name) continue;\n\n // add the documentation into a dictionary\n this.documentation[name.toLowerCase()] = doc;\n doc._plugin = plugin;\n\n // add all the commands into a dictionary\n if (doc.commands) {\n for (const cmd of doc.commands) {\n this.commands[cmd.name.toLowerCase()] = cmd;\n cmd._plugin = plugin;\n }\n }\n }\n }\n}\n"],"names":["pluginSchema.pluginConfig","eq","pluginSchema.pluginStore","and","count","soft","openDb","drizzle","runPluginMigrations","Logger","RpcPlugin","NodeVmPlugin","NodePlugin","importNedbIfNeeded","desc"],"mappings":"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"}
|