diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 01ca103..824a484 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -6,6 +6,7 @@ export abstract class ExtensionBase implements Extension { admin_only = false tables = false disabled = false + hidden = false initialized_deps: DependencyMap = new DependencyMapImpl() name: Extension['name'] = "default_name" title: Extension['title'] = "Default Title" diff --git a/src/extensions/admin/index.html b/src/extensions/admin/index.html index 2535a42..c59efb4 100644 --- a/src/extensions/admin/index.html +++ b/src/extensions/admin/index.html @@ -6,6 +6,8 @@ {% block body %} +Invites +
diff --git a/src/extensions/admin/static/index.css b/src/extensions/admin/static/index.css index 7221834..025a5cb 100644 --- a/src/extensions/admin/static/index.css +++ b/src/extensions/admin/static/index.css @@ -1,3 +1,7 @@ +#content { + padding-top: 10px; +} + #user_table{ border-collapse: collapse; margin: 10px, 0; diff --git a/src/extensions/invite/index.ts b/src/extensions/invite/index.ts index ab4b9ca..d909461 100644 --- a/src/extensions/invite/index.ts +++ b/src/extensions/invite/index.ts @@ -7,6 +7,7 @@ export default class extends ExtensionBase { override name = 'invite' override title = 'Invite' override tables = true + override hidden = true salt: string @@ -109,6 +110,8 @@ export default class extends ExtensionBase { .query('_minecraft_minecraft') // @ts-expect-error .insert({minecraft_name: form.minecraft_name, user_id: id}) + + ;(ctx.context.extensions.get('minecraft') as any)?.update_whitelist() } return this.return_html(ctx, 'login', undefined, 500, 303, { diff --git a/src/extensions/minecraft/index.html b/src/extensions/minecraft/index.html index dd55a7d..0757e23 100644 --- a/src/extensions/minecraft/index.html +++ b/src/extensions/minecraft/index.html @@ -10,4 +10,8 @@
{{auth_err}} + + +Updating the whitelist can take up to a minute, please be patient. + {% endblock %} diff --git a/src/extensions/minecraft/index.ts b/src/extensions/minecraft/index.ts index 036ea9b..3bb36c2 100644 --- a/src/extensions/minecraft/index.ts +++ b/src/extensions/minecraft/index.ts @@ -1,6 +1,7 @@ import { ExtensionBase } from "../../modules.js" import Knex from "../../modules/knex.ts" import { unpack } from "../../util.ts" +import * as rcon from "./lib.ts" export default class extends ExtensionBase { override name = 'minecraft' @@ -8,7 +9,13 @@ export default class extends ExtensionBase { override tables = true override init: Extension['init'] = async (context) => { - return ExtensionBase.init(this, context) + // Init super here as rest of init happens async + const result = ExtensionBase.init(this, context) + + // On a separate "thread" in case we can't connect to the RCON server immediately + setTimeout(this.update_whitelist, 0) + + return result } override handle: Extension['handle'] = async (ctx) => { @@ -34,7 +41,7 @@ export default class extends ExtensionBase { case 'change':{ if (ctx.data) { - let new_MCName = ctx.data.form.minecraft_name + let new_MCName = ctx.data.form.minecraft_name ?? '' let [name, err] = await knex .query('_minecraft') .select('minecraft_name') @@ -51,10 +58,51 @@ export default class extends ExtensionBase { // @ts-expect-error .insert({minecraft_name: new_MCName, user_id: ctx.context.user?.id}) } + + // Intentionally not awaited as it can take a while + this.update_whitelist() } return this.return(ctx, undefined, location='/minecraft') } } } + + update_whitelist = async () => { + const [knex]: [Knex] = this.get_dependencies('Knex') + const [raw_names, err] = await knex + .query('_minecraft') + .select('minecraft_name') + .then(unpack<{minecraft_name: string}[]>, unpack) + if (err) + return + const names: string[] = (raw_names ?? []) + // Unpack objects + .flatMap((name) => name.minecraft_name) + // Remove empty rows + .filter((name) => name != '') + + const currently_whitelisted_raw = await rcon.send("whitelist list") + const currently_whitelisted = currently_whitelisted_raw + .split(' ') + // Remove trash + .slice(5) + .flatMap((name) => { + if (name.endsWith(',')) + return name.substring(0, name.length -1) + return name + }) + + const to_remove = currently_whitelisted.filter((name) => !names.includes(name)) + const to_add = names.filter((name) => !currently_whitelisted.includes(name)) + + for (const name of to_remove) { + const response = await rcon.send(`whitelist remove ${name}`) + console.log(response) + } + for (const name of to_add) { + const response = await rcon.send(`whitelist add ${name}`) + console.log(response) + } + } } diff --git a/src/extensions/minecraft/lib.ts b/src/extensions/minecraft/lib.ts new file mode 100644 index 0000000..291e67a --- /dev/null +++ b/src/extensions/minecraft/lib.ts @@ -0,0 +1,60 @@ +import { Rcon } from "rcon-client" + +function log(...args: any[]) { + console.log("[MINECRAFT]:",...args) +} + +let is_connected = false + +const rcon = new Rcon({ + host: '127.0.0.1', + port: 25575, + password: '1234', +}) +export const raw = rcon + +export const send = async (command: string) => { + await connected() + return rcon.send(command).catch(() => '') +} +export const sendRaw = async (buffer: Buffer) => { + await connected() + return rcon.sendRaw(buffer).catch(() => '') +} + +export const connected = async (): Promise => new Promise((resolve) => { + const loop = setInterval(async () => { + if (!is_connected) { + await rcon.connect().then( + () => { + is_connected = true + clearInterval(loop) + resolve() + }, + (err: Error) => { + is_connected = false + log("Failed connecting:", err.message) + } + ) + } + else { + clearInterval(loop) + resolve() + } + }, 5000) +}) + +rcon.on('connect', () => { + is_connected = true + log("connected") +}) +rcon.on('end', () => { + is_connected = false + log("disconnected") +}) +rcon.on('error', (err) => { + log("err: ", err) +}) +rcon.on('authenticated', () => { + log("authenticated") +}) diff --git a/src/extensions/minecraft/package-lock.json b/src/extensions/minecraft/package-lock.json index 343e8e2..49e7b6c 100644 --- a/src/extensions/minecraft/package-lock.json +++ b/src/extensions/minecraft/package-lock.json @@ -8,13 +8,22 @@ "name": "minecraft", "version": "0.0.1", "dependencies": { - "rcon-srcds": "2.1.0" + "rcon-client": "^4.2.5" } }, - "node_modules/rcon-srcds": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rcon-srcds/-/rcon-srcds-2.1.0.tgz", - "integrity": "sha512-kkF32wt+xsX19b6wrerU8qTOHa9AX81eJTffgZD0FfaVV2U+sL1gFr5W2VX4m+ILh/l4ZEAYBHx653l2Ci+h+Q==", + "node_modules/rcon-client": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/rcon-client/-/rcon-client-4.2.5.tgz", + "integrity": "sha512-AnX1GU/ZTlwtYup3H6h0J1hwfP3OYltXVe+8ReBzmNEepX3xGH8nDg7gYqT5Y9rpAS/LmQ48h0BKINt1YGd8bA==", + "license": "MIT", + "dependencies": { + "typed-emitter": "^0.1.0" + } + }, + "node_modules/typed-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz", + "integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg==", "license": "MIT" } } diff --git a/src/extensions/minecraft/package.json b/src/extensions/minecraft/package.json index 3e69605..8c967cc 100644 --- a/src/extensions/minecraft/package.json +++ b/src/extensions/minecraft/package.json @@ -3,6 +3,6 @@ "version": "0.0.1", "type": "module", "dependencies": { - "rcon-srcds": "2.1.0" + "rcon-client": "^4.2.5" } } diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts index 5b6354a..9c76711 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -162,17 +162,17 @@ export default class extends ExtensionBase implements RootExtension { else return new Error('Wrong name or password') } - else if (ip.startsWith(subnet)) { - // Try using IP-address if no name and password - const user = await knex - .query({u: 'user', p: '_profile_device'}) - .select('u.*') - .join('_profile_device', 'u.id', '=', 'p.user_id') - .where('p.ip', ip) - .first() - - return user - } + // else if (ip.startsWith(subnet)) { + // // Try using IP-address if no name and password + // const user = await knex + // .query({u: 'user', p: '_profile_device'}) + // .select('u.*') + // .join('_profile_device', 'u.id', '=', 'p.user_id') + // .where('p.ip', ip) + // .first() + + // return user + // } } private decrypt_auth(auth: BasicAuth): [name: string, password: string] | Error { diff --git a/src/templates/includes/extensions.html b/src/templates/includes/extensions.html index 736f0aa..5cb4369 100644 --- a/src/templates/includes/extensions.html +++ b/src/templates/includes/extensions.html @@ -1,6 +1,8 @@
{% for _,ext in extensions %} - {{ext.title}} + {% if not ext.hidden %} + {{ext.title}} + {% endif %} {% endfor %}
diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts index cf7f1c5..1a4fbb6 100644 --- a/types/classes/extension.d.ts +++ b/types/classes/extension.d.ts @@ -2,6 +2,7 @@ declare interface Extension { admin_only: boolean tables: boolean disabled: boolean + hidden: boolean name: string title: string