Skip to content

Commit a40b76f

Browse files
committed
Add whitelist module
1 parent cbf9a01 commit a40b76f

File tree

13 files changed

+934
-3
lines changed

13 files changed

+934
-3
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
config.json
2+
whitelist.json
3+
usercache.json
24

35
package-lock.json
46
node_modules/

index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {REST} from '@discordjs/rest'
44
import {Routes} from 'discord-api-types/v9'
55

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

99
client.on('ready', () => {
1010
console.log(`Logged in as ${client.user.tag}!`)

modules/whitelist/commands/pipe.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import fs from 'fs/promises'
2+
3+
export async function sendCommands(pipe, ...commands) {
4+
await fs.writeFile(pipe, commands.map(cmd => cmd + '\n').join(), {flag: 'a'})
5+
}

modules/whitelist/commands/rcon.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import util from 'minecraft-server-util'
2+
3+
const {RCON} = util
4+
5+
const connections = {}
6+
const TIMEOUT = Symbol()
7+
8+
export async function sendCommands(params, ...commands) {
9+
const connection = await ensureConnection(params)
10+
const results = []
11+
for (const command of commands) {
12+
results.push(await connection.execute(command))
13+
}
14+
return results
15+
}
16+
17+
async function ensureConnection(params) {
18+
const key = params.host + ':' + params.port
19+
let connection = connections[key]
20+
if (!connection) {
21+
connection = new RCON(params.host, {port: params.port, password: params.password})
22+
await connection.connect()
23+
connections[key] = connection
24+
}
25+
connection[TIMEOUT] = Math.max(Date.now() + 5000, connection[TIMEOUT] || 0)
26+
setTimeout(checkRemoveConnection.bind(null, key), 5100)
27+
return connection
28+
}
29+
30+
async function checkRemoveConnection(key) {
31+
const connection = connections[key]
32+
if (connection[TIMEOUT] <= Date.now()) {
33+
delete connections[key]
34+
try {
35+
await connection.close()
36+
} catch (e) {
37+
console.error(e)
38+
}
39+
}
40+
}

modules/whitelist/database.js

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import fs, { read } from 'fs'
2+
import { getUUID } from './usercache.js'
3+
4+
export class Database {
5+
#file
6+
#users = {}
7+
#removed = new Set()
8+
9+
constructor(file) {
10+
this.#file = file
11+
this.load()
12+
this.save()
13+
}
14+
15+
load() {
16+
if (!fs.existsSync(this.#file)) return
17+
const data = JSON.parse(fs.readFileSync(this.#file))
18+
this.#users = data.users
19+
for (const user in this.#users) {
20+
this.#users[user].uuids = this.#users[user].uuids || []
21+
}
22+
this.#removed = new Set(data.removed || [])
23+
}
24+
25+
save() {
26+
fs.writeFileSync(this.#file, JSON.stringify(this.dump(), null, 2))
27+
}
28+
29+
dump() {
30+
return {
31+
users: this.#users,
32+
removed: [...this.#removed]
33+
}
34+
}
35+
36+
async convertNamesToUuids() {
37+
const names = new Set()
38+
for (const user in this.#users) {
39+
for (const name of this.#users[user].names || []) {
40+
names.add(name)
41+
}
42+
}
43+
if (!names.size) return false
44+
const uuids = await getUUID([...names])
45+
for (const user in this.#users) {
46+
const userUuids = new Set(this.#users[user].uuids)
47+
for (const name of this.#users[user].names || []) {
48+
const uuidForName = uuids[name.toLowerCase()]
49+
if (!uuidForName) {
50+
console.warn('Invalid username ' + name + ', skipping')
51+
} else {
52+
userUuids.add(uuidForName)
53+
}
54+
}
55+
this.#users[user].uuids = [...userUuids]
56+
delete this.#users[user].names
57+
}
58+
for (const name in uuids) this.#removed.delete(uuids[name])
59+
this.save()
60+
return true
61+
}
62+
63+
getUser(id) {
64+
return this.#users[id] || {uuids: []}
65+
}
66+
67+
getAllByUUID() {
68+
const byUuid = {}
69+
for (const id in this.#users) {
70+
for (const uuid of this.#users[id].uuids) {
71+
byUuid[uuid] = id
72+
}
73+
}
74+
return byUuid
75+
}
76+
77+
getBannedUUIDs() {
78+
const banned = new Set()
79+
for (const user of Object.values(this.#users)) {
80+
if (!user.banned) continue
81+
for (const uuid of user.uuids) {
82+
banned.add(uuid)
83+
}
84+
}
85+
return banned
86+
}
87+
88+
getLinkedUser(uuid) {
89+
for (const id in this.#users) {
90+
if (this.#users[id].uuids.includes(uuid)) return {id, user: this.#users[id]}
91+
}
92+
}
93+
94+
linkUser(id, uuid) {
95+
const user = this.getUser(id)
96+
user.uuids = [...new Set([...user.uuids, uuid])]
97+
this.#users[id] = user
98+
this.#removed.delete(uuid)
99+
this.save()
100+
return user
101+
}
102+
103+
unlinkUser(id, uuid) {
104+
const user = this.getUser(id)
105+
const uuids = new Set(user.uuids)
106+
uuids.delete(uuid)
107+
user.uuids = [...uuids]
108+
if (user.uuids.length) {
109+
this.#users[id] = user
110+
} else {
111+
delete this.#users[id]
112+
}
113+
this.#removed.add(uuid)
114+
this.save()
115+
return user
116+
}
117+
118+
removeUser(id) {
119+
const user = this.#users[id]
120+
delete this.#users[id]
121+
for (const uuid of user.uuids) {
122+
this.#removed.add(uuid)
123+
}
124+
this.save()
125+
return user
126+
}
127+
128+
get removed() {
129+
return [...this.#removed]
130+
}
131+
}

modules/whitelist/fs/ftp.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import path from 'path'
2+
import Client from 'ftp'
3+
import {readFully} from '../../../utils.js'
4+
5+
export class FTPFileManager {
6+
#params
7+
8+
constructor(params) {
9+
this.#params = params
10+
}
11+
12+
async #connect() {
13+
return new Promise((resolve, reject) => {
14+
const c = new Client()
15+
c.on('ready', () => resolve(c))
16+
c.on('error', reject)
17+
c.connect({
18+
host: this.#params.host,
19+
port: this.#params.port || 21,
20+
user: this.#params.username,
21+
password: this.#params.password
22+
})
23+
})
24+
}
25+
26+
async readFile(file) {
27+
const c = await this.#connect()
28+
return new Promise((resolve, reject) => {
29+
c.get(path.resolve('/', this.#params.path, file), async (err, stream) => {
30+
if (err) {
31+
reject(err)
32+
return
33+
}
34+
const data = await readFully(stream)
35+
c.end()
36+
resolve(data)
37+
})
38+
})
39+
}
40+
41+
async writeFile(file, data) {
42+
const c = await this.#connect()
43+
return new Promise((resolve, reject) => {
44+
c.put(data, path.resolve('/', this.#params.path, file), err => {
45+
if (err) {
46+
reject(err)
47+
} else {
48+
resolve()
49+
}
50+
})
51+
})
52+
}
53+
}

modules/whitelist/fs/local.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import fs from 'fs/promises'
2+
import path from 'path'
3+
4+
export class LocalFileManager {
5+
#path
6+
7+
constructor(params) {
8+
this.#path = params.path
9+
}
10+
11+
async readFile(file) {
12+
return fs.readFile(path.resolve(this.#path, file))
13+
}
14+
15+
async writeFile(file, data) {
16+
return fs.writeFile(path.resolve(this.#path, file), data)
17+
}
18+
}

modules/whitelist/fs/sftp.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import {Client} from 'ssh2'
4+
import {readFully} from '../../../utils.js'
5+
6+
export class SFTPFileManager {
7+
#params
8+
9+
constructor(params) {
10+
this.#params = {
11+
...params,
12+
privateKey: params.privateKey && fs.readFileSync(params.privateKey)
13+
}
14+
}
15+
16+
async readFile(file) {
17+
const fullPath = path.resolve(this.#params.path, file)
18+
return new Promise((resolve, reject) => {
19+
const conn = new Client()
20+
conn.on('ready', () => {
21+
conn.sftp((err, sftp) => {
22+
if (err) return reject(err)
23+
readFully(sftp.createReadStream(fullPath, {encoding: 'utf8'})).then(data => {
24+
conn.end()
25+
resolve(data)
26+
}).catch(reject)
27+
})
28+
}).connect(this.#params)
29+
})
30+
}
31+
32+
async writeFile(file, data) {
33+
const fullPath = path.resolve(this.#params.path, file)
34+
return new Promise((resolve, reject) => {
35+
const conn = new Client()
36+
conn.on('ready', () => {
37+
conn.sftp((err, sftp) => {
38+
if (err) return reject(err)
39+
const stream = sftp.createWriteStream(fullPath, {encoding: 'utf8'})
40+
stream.on('error', reject)
41+
stream.end(data, () => {
42+
conn.end()
43+
resolve()
44+
})
45+
})
46+
}).connect(this.#params)
47+
})
48+
}
49+
}

0 commit comments

Comments
 (0)