Skip to content

Commit

Permalink
Add whitelist module
Browse files Browse the repository at this point in the history
  • Loading branch information
skyrising committed Feb 20, 2022
1 parent cbf9a01 commit a40b76f
Show file tree
Hide file tree
Showing 13 changed files with 934 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
config.json
whitelist.json
usercache.json

package-lock.json
node_modules/
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {REST} from '@discordjs/rest'
import {Routes} from 'discord-api-types/v9'

const config = JSON.parse(fs.readFileSync('./config.json'))
const client = new Client({intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]})
const client = new Client({intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS]})

client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`)
Expand Down
5 changes: 5 additions & 0 deletions modules/whitelist/commands/pipe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import fs from 'fs/promises'

export async function sendCommands(pipe, ...commands) {
await fs.writeFile(pipe, commands.map(cmd => cmd + '\n').join(), {flag: 'a'})
}
40 changes: 40 additions & 0 deletions modules/whitelist/commands/rcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import util from 'minecraft-server-util'

const {RCON} = util

const connections = {}
const TIMEOUT = Symbol()

export async function sendCommands(params, ...commands) {
const connection = await ensureConnection(params)
const results = []
for (const command of commands) {
results.push(await connection.execute(command))
}
return results
}

async function ensureConnection(params) {
const key = params.host + ':' + params.port
let connection = connections[key]
if (!connection) {
connection = new RCON(params.host, {port: params.port, password: params.password})
await connection.connect()
connections[key] = connection
}
connection[TIMEOUT] = Math.max(Date.now() + 5000, connection[TIMEOUT] || 0)
setTimeout(checkRemoveConnection.bind(null, key), 5100)
return connection
}

async function checkRemoveConnection(key) {
const connection = connections[key]
if (connection[TIMEOUT] <= Date.now()) {
delete connections[key]
try {
await connection.close()
} catch (e) {
console.error(e)
}
}
}
131 changes: 131 additions & 0 deletions modules/whitelist/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import fs, { read } from 'fs'
import { getUUID } from './usercache.js'

export class Database {
#file
#users = {}
#removed = new Set()

constructor(file) {
this.#file = file
this.load()
this.save()
}

load() {
if (!fs.existsSync(this.#file)) return
const data = JSON.parse(fs.readFileSync(this.#file))
this.#users = data.users
for (const user in this.#users) {
this.#users[user].uuids = this.#users[user].uuids || []
}
this.#removed = new Set(data.removed || [])
}

save() {
fs.writeFileSync(this.#file, JSON.stringify(this.dump(), null, 2))
}

dump() {
return {
users: this.#users,
removed: [...this.#removed]
}
}

async convertNamesToUuids() {
const names = new Set()
for (const user in this.#users) {
for (const name of this.#users[user].names || []) {
names.add(name)
}
}
if (!names.size) return false
const uuids = await getUUID([...names])
for (const user in this.#users) {
const userUuids = new Set(this.#users[user].uuids)
for (const name of this.#users[user].names || []) {
const uuidForName = uuids[name.toLowerCase()]
if (!uuidForName) {
console.warn('Invalid username ' + name + ', skipping')
} else {
userUuids.add(uuidForName)
}
}
this.#users[user].uuids = [...userUuids]
delete this.#users[user].names
}
for (const name in uuids) this.#removed.delete(uuids[name])
this.save()
return true
}

getUser(id) {
return this.#users[id] || {uuids: []}
}

getAllByUUID() {
const byUuid = {}
for (const id in this.#users) {
for (const uuid of this.#users[id].uuids) {
byUuid[uuid] = id
}
}
return byUuid
}

getBannedUUIDs() {
const banned = new Set()
for (const user of Object.values(this.#users)) {
if (!user.banned) continue
for (const uuid of user.uuids) {
banned.add(uuid)
}
}
return banned
}

getLinkedUser(uuid) {
for (const id in this.#users) {
if (this.#users[id].uuids.includes(uuid)) return {id, user: this.#users[id]}
}
}

linkUser(id, uuid) {
const user = this.getUser(id)
user.uuids = [...new Set([...user.uuids, uuid])]
this.#users[id] = user
this.#removed.delete(uuid)
this.save()
return user
}

unlinkUser(id, uuid) {
const user = this.getUser(id)
const uuids = new Set(user.uuids)
uuids.delete(uuid)
user.uuids = [...uuids]
if (user.uuids.length) {
this.#users[id] = user
} else {
delete this.#users[id]
}
this.#removed.add(uuid)
this.save()
return user
}

removeUser(id) {
const user = this.#users[id]
delete this.#users[id]
for (const uuid of user.uuids) {
this.#removed.add(uuid)
}
this.save()
return user
}

get removed() {
return [...this.#removed]
}
}
53 changes: 53 additions & 0 deletions modules/whitelist/fs/ftp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import path from 'path'
import Client from 'ftp'
import {readFully} from '../../../utils.js'

export class FTPFileManager {
#params

constructor(params) {
this.#params = params
}

async #connect() {
return new Promise((resolve, reject) => {
const c = new Client()
c.on('ready', () => resolve(c))
c.on('error', reject)
c.connect({
host: this.#params.host,
port: this.#params.port || 21,
user: this.#params.username,
password: this.#params.password
})
})
}

async readFile(file) {
const c = await this.#connect()
return new Promise((resolve, reject) => {
c.get(path.resolve('/', this.#params.path, file), async (err, stream) => {
if (err) {
reject(err)
return
}
const data = await readFully(stream)
c.end()
resolve(data)
})
})
}

async writeFile(file, data) {
const c = await this.#connect()
return new Promise((resolve, reject) => {
c.put(data, path.resolve('/', this.#params.path, file), err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
}
18 changes: 18 additions & 0 deletions modules/whitelist/fs/local.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import fs from 'fs/promises'
import path from 'path'

export class LocalFileManager {
#path

constructor(params) {
this.#path = params.path
}

async readFile(file) {
return fs.readFile(path.resolve(this.#path, file))
}

async writeFile(file, data) {
return fs.writeFile(path.resolve(this.#path, file), data)
}
}
49 changes: 49 additions & 0 deletions modules/whitelist/fs/sftp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from 'fs'
import path from 'path'
import {Client} from 'ssh2'
import {readFully} from '../../../utils.js'

export class SFTPFileManager {
#params

constructor(params) {
this.#params = {
...params,
privateKey: params.privateKey && fs.readFileSync(params.privateKey)
}
}

async readFile(file) {
const fullPath = path.resolve(this.#params.path, file)
return new Promise((resolve, reject) => {
const conn = new Client()
conn.on('ready', () => {
conn.sftp((err, sftp) => {
if (err) return reject(err)
readFully(sftp.createReadStream(fullPath, {encoding: 'utf8'})).then(data => {
conn.end()
resolve(data)
}).catch(reject)
})
}).connect(this.#params)
})
}

async writeFile(file, data) {
const fullPath = path.resolve(this.#params.path, file)
return new Promise((resolve, reject) => {
const conn = new Client()
conn.on('ready', () => {
conn.sftp((err, sftp) => {
if (err) return reject(err)
const stream = sftp.createWriteStream(fullPath, {encoding: 'utf8'})
stream.on('error', reject)
stream.end(data, () => {
conn.end()
resolve()
})
})
}).connect(this.#params)
})
}
}
Loading

0 comments on commit a40b76f

Please sign in to comment.