From 95789a6931cf8d5e6f93e683b6179af3382cd0fc Mon Sep 17 00:00:00 2001 From: sophie Date: Mon, 1 Jul 2024 15:39:17 +0300 Subject: [PATCH] first commit --- .gitignore | 176 +++++++++++++++++++++++++++++++++ README.md | 17 ++++ bun.lockb | Bin 0 -> 14199 bytes config.example.json | 10 ++ index.ts | 128 ++++++++++++++++++++++++ package.json | 18 ++++ rcon.ts | 235 ++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 27 +++++ 8 files changed, 611 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 config.example.json create mode 100644 index.ts create mode 100644 package.json create mode 100644 rcon.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3d6cdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +config.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e8aabd --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# autowhitelist + +This ts project automatically whitelists people to a minecraft server from a discord. It's very very simple. +Supports multiple users in one message, e.g "USERONE", "USERTWO". This isn't configurable, but quite easy to remove. + +All whitelists can also be removed by just deleting the message, helping both users incase username changes or other issues, and staff. + +Run `bun i`, copy `config.example.json` to `config.json` and change all of the settings. The channel option is the channel ID, which you can copy by using developer mode on discord. Then, just start the bot. +If you get something as such from the bot, then everything's done right: +``` +RCON connected. +RCON authenicated. +Ready! Logged in as Slop Bridge#2702 +``` + +There is also an option to get rid of nicknames changes. Set "changeNicknames" to "false". +This project was created using `bun init` in bun v1.1.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..2f27a09a609d0f1bf2fa923830500f93bef7778f GIT binary patch literal 14199 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 { + console.log("RCON connected."); +}); + +rcon.on("auth", () => { + console.log("RCON authenicated."); +}); + +client.once(Events.ClientReady, async (readyClient) => { + console.log(`Ready! Logged in as ${readyClient.user.tag}`); + let playerUsernames: string[] = []; + + const channel = (await client.channels.fetch(config.channel)) as TextChannel; + + const messages = await channel.messages.fetch(); + + for await (const f of messages) { + playerUsernames = playerUsernames.concat(f[1].content.split("\n")); + if (!f[1].reactions.resolve("✅")) await f[1].react("✅"); + let member = f[1].member; + + if (!member) member = await f[1].guild.members.fetch(f[1].author.id); + if (config.changeNicknames) { + if (member.nickname !== f[1].content.split("\n")[0]) { + try { + await member.setNickname(f[1].content.split("\n")[0], "whitelisted"); + } catch {} + } + } + } + + for (let i = 0; i < playerUsernames.length; i++) { + setTimeout(() => { + rcon.send("whitelist add " + playerUsernames[i]); + }, i * 20); + } + + console.log("Mass-whitelisted."); +}); + +client.on(Events.MessageCreate, async (message) => { + if ( + message.channelId == config.channel && + message.author!.id != client.user!.id + ) { + const channel = (await client.channels.fetch( + config.channel + )) as TextChannel; + + const messages = await channel.messages.fetch(); + + if (messages.filter((z) => z.author.id == message.author.id).size > 1) { + await message.delete(); + const deleteThisMessage = await channel.send( + `<@${message.author.id}>, you can only have one message in this channel.` + ); + + setTimeout(async () => { + await deleteThisMessage.delete(); + }, 5000); + return; + } + + const playerUsernames = message.content.split("\n"); + + let member = message.member; + + if (!member) member = await message.guild!.members.fetch(message.author.id); + if (config.changeNicknames) { + try { + await member.setNickname(playerUsernames[0]); + } catch {} + } + for (let i = 0; i < playerUsernames.length; i++) { + setTimeout(() => { + rcon.send("whitelist add " + playerUsernames[i]); + }, i * 20); + } + + await message.react("✅"); + } +}); + +client.on(Events.MessageDelete, async (message) => { + if ( + message.channelId == config.channel && + message.author!.id != client.user!.id + ) { + if (message.reactions.resolve("✅")) { + const deleteThisMessage = await message.channel.send( + `<@${message.author?.id}>, you are no longer whitelisted.` + ); + + const member = await message.guild!.members.fetch(message.author!.id); + if (config.changeNicknames) { + try { + await member.setNickname(null, "unwhitelisted"); + } catch {} + setTimeout(async () => { + await deleteThisMessage.delete(); + }, 5000); + } + const usernames = message.content?.split("\n") || []; + + for (let i = 0; i < usernames.length; i++) { + setTimeout(() => { + rcon.send("whitelist remove " + usernames[i]); + }, i * 20); + } + } + } +}); + +client.login(config.token); +rcon.connect(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..6499df5 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "autowhitelist", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "typed-emitter": "^2.1.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "bufferutil": "^4.0.8", + "discord.js": "^14.15.3", + "utf-8-validate": "^6.0.4", + "zlib-sync": "^0.1.9" + } +} \ No newline at end of file diff --git a/rcon.ts b/rcon.ts new file mode 100644 index 0000000..69de561 --- /dev/null +++ b/rcon.ts @@ -0,0 +1,235 @@ +import EventEmitter from "events"; +import * as net from "net"; +import * as dgram from "dgram"; +import { Buffer } from "buffer"; +import type TypedEmitter from "typed-emitter"; + +type Events = { + error: (error: Error) => void; + auth: () => void; + response: (response: string) => void; + connect: () => void; + end: () => void; + done: () => void; +}; + +export const PacketType = { + COMMAND: 0x02, + AUTH: 0x03, + RESPONSE_VALUE: 0x00, + RESPONSE_AUTH: 0x02, +}; + +interface Options { + tcp?: boolean; + challenge?: boolean; + id?: number; +} +export class Rcon extends (EventEmitter as new () => TypedEmitter) { + private host: string; + private port: number; + private password: string; + private rconId: number; + private hasAuthed: boolean; + private outstandingData: Uint8Array | null; + private tcp: boolean; + private challenge: boolean; + private _challengeToken: string; + private _tcpSocket!: net.Socket; + private _udpSocket!: dgram.Socket; + + constructor(host: string, port: number, password: string, options?: Options) { + super(); + options = options || {}; + this.host = host; + this.port = port; + this.password = password; + this.rconId = options.id || 0x0012d4a6; // This is arbitrary in most cases + this.hasAuthed = false; + this.outstandingData = null; + this.tcp = options.tcp ? options.tcp : true; + this.challenge = options.challenge ? options.challenge : true; + this._challengeToken = ""; + } + + public send = (data: string, cmd?: number, id?: number): void => { + let sendBuf: Buffer; + if (this.tcp) { + cmd = cmd || PacketType.COMMAND; + id = id || this.rconId; + + const length = Buffer.byteLength(data); + sendBuf = Buffer.alloc(length + 14); + sendBuf.writeInt32LE(length + 10, 0); + sendBuf.writeInt32LE(id, 4); + sendBuf.writeInt32LE(cmd, 8); + sendBuf.write(data, 12); + sendBuf.writeInt16LE(0, length + 12); + } else { + if (this.challenge && !this._challengeToken) { + this.emit("error", new Error("Not authenticated")); + return; + } + let str = "rcon "; + if (this._challengeToken) str += this._challengeToken + " "; + if (this.password) str += this.password + " "; + str += data + "\n"; + sendBuf = Buffer.alloc(4 + Buffer.byteLength(str)); + sendBuf.writeInt32LE(-1, 0); + sendBuf.write(str, 4); + } + this._sendSocket(sendBuf); + }; + + private _sendSocket = (buf: Buffer) => { + if (this._tcpSocket) { + this._tcpSocket.write(buf.toString("binary"), "binary"); + } else if (this._udpSocket) { + this._udpSocket.send(buf, 0, buf.length, this.port, this.host); + } + }; + + public connect = (): void => { + if (this.tcp) { + this._tcpSocket = net.createConnection(this.port, this.host); + this._tcpSocket + .on("data", (data) => { + this._tcpSocketOnData(data); + }) + .on("connect", () => { + this.socketOnConnect(); + }) + .on("error", (err) => { + this.emit("error", err); + }) + .on("end", () => { + this.socketOnEnd(); + }); + } else { + this._udpSocket = dgram.createSocket("udp4"); + this._udpSocket + .on("message", (data) => { + this._udpSocketOnData(data); + }) + .on("listening", () => { + this.socketOnConnect(); + }) + .on("error", (err) => { + this.emit("error", err); + }) + .on("close", () => { + this.socketOnEnd(); + }); + this._udpSocket.bind(0); + } + }; + + public disconnect = (): void => { + if (this._tcpSocket) this._tcpSocket.end(); + if (this._udpSocket) this._udpSocket.close(); + }; + + public setTimeout = (timeout: number, callback: () => void): void => { + if (!this._tcpSocket) return; + this._tcpSocket.setTimeout(timeout, () => { + this._tcpSocket.end(); + if (callback) callback(); + }); + }; + + private _udpSocketOnData = (data: Buffer) => { + const a = data.readUInt32LE(0); + if (a === 0xffffffff) { + const str = data.toString("utf-8", 4); + const tokens = str.split(" "); + if ( + tokens.length === 3 && + tokens[0] === "challenge" && + tokens[1] === "rcon" + ) { + this._challengeToken = tokens[2] + .substring(0, tokens[2].length - 1) + .trim(); + this.hasAuthed = true; + this.emit("auth"); + } else { + this.emit("response", str.substring(1, str.length - 2)); + } + } else { + this.emit("error", new Error("Received malformed packet")); + } + }; + + private _tcpSocketOnData = (data: Buffer) => { + if (this.outstandingData != null) { + data = Buffer.concat( + [this.outstandingData, data], + this.outstandingData.length + data.length + ); + this.outstandingData = null; + } + + while (data.length) { + const len = data.readInt32LE(0); + if (!len) return; + + const id = data.readInt32LE(4); + const type = data.readInt32LE(8); + + if (len >= 10 && data.length >= len + 4) { + if (id === this.rconId) { + if (!this.hasAuthed && type === PacketType.RESPONSE_AUTH) { + this.hasAuthed = true; + this.emit("auth"); + } else if (type === PacketType.RESPONSE_VALUE) { + // Read just the body of the packet (truncate the last null byte) + // See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol for details + let str = data.toString("utf8", 12, 12 + len - 10); + + if (str.charAt(str.length - 1) === "\n") { + // Emit the response without the newline. + str = str.substring(0, str.length - 1); + } + + this.emit("response", str); + } + } else { + this.emit("error", new Error("Authentication failed")); + } + + data = data.slice(12 + len - 8); + } else { + // Keep a reference to the chunk if it doesn't represent a full packet + this.outstandingData = data; + break; + } + } + }; + + public socketOnConnect = (): void => { + this.emit("connect"); + + if (this.tcp) { + this.send(this.password, PacketType.AUTH); + } else if (this.challenge) { + const str = "challenge rcon\n"; + const sendBuf = Buffer.alloc(str.length + 4); + sendBuf.writeInt32LE(-1, 0); + sendBuf.write(str, 4); + this._sendSocket(sendBuf); + } else { + const sendBuf = Buffer.alloc(5); + sendBuf.writeInt32LE(-1, 0); + sendBuf.writeUInt8(0, 4); + this._sendSocket(sendBuf); + + this.hasAuthed = true; + this.emit("auth"); + } + }; + + public socketOnEnd = (): void => { + this.emit("end"); + this.hasAuthed = false; + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}