Spaces:
Sleeping
Sleeping
Initial commit
Browse files- .gitignore +10 -0
- .prettierrc +6 -0
- Dockerfile +14 -0
- config.json +5 -0
- eslint.config.mjs +27 -0
- package-lock.json +0 -0
- package.json +33 -0
- src/bot/bot.ts +58 -0
- src/config/config.ts +55 -0
- src/index.ts +21 -0
- src/logger/logger.ts +39 -0
- src/server/server.ts +58 -0
- tsconfig.json +34 -0
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
dist
|
| 3 |
+
coverage
|
| 4 |
+
|
| 5 |
+
.env
|
| 6 |
+
.env.*
|
| 7 |
+
|
| 8 |
+
*.log
|
| 9 |
+
|
| 10 |
+
.vscode/settings.json
|
.prettierrc
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"semi": true,
|
| 3 |
+
"singleQuote": true,
|
| 4 |
+
"trailingComma": "es5",
|
| 5 |
+
"printWidth": 100
|
| 6 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY package*.json ./
|
| 6 |
+
RUN npm install
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
RUN npm run build
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
|
| 14 |
+
CMD ["npm", "start"]
|
config.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"minecraft_host": "NSMP2.play.hosting",
|
| 3 |
+
"minecraft_port": 25565,
|
| 4 |
+
"minecraft_version": "1.21.1"
|
| 5 |
+
}
|
eslint.config.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from "@eslint/js";
|
| 2 |
+
import globals from "globals";
|
| 3 |
+
import tseslint from "typescript-eslint";
|
| 4 |
+
import prettier from "eslint-config-prettier";
|
| 5 |
+
|
| 6 |
+
export default tseslint.config(
|
| 7 |
+
js.configs.recommended,
|
| 8 |
+
|
| 9 |
+
...tseslint.configs.recommended,
|
| 10 |
+
|
| 11 |
+
{
|
| 12 |
+
files: ["**/*.ts"],
|
| 13 |
+
|
| 14 |
+
languageOptions: {
|
| 15 |
+
globals: {
|
| 16 |
+
...globals.node
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
|
| 20 |
+
rules: {
|
| 21 |
+
"@typescript-eslint/no-explicit-any": "warn",
|
| 22 |
+
"@typescript-eslint/no-unused-vars": "off"
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
|
| 26 |
+
prettier
|
| 27 |
+
);
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "server",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"main": "index.js",
|
| 5 |
+
"scripts": {
|
| 6 |
+
"build": "tsc",
|
| 7 |
+
"check": "prettier . --check",
|
| 8 |
+
"lint": "eslint .",
|
| 9 |
+
"start": "node dist/index.js",
|
| 10 |
+
"dev": "tsc --watch",
|
| 11 |
+
"format": "prettier . --write"
|
| 12 |
+
},
|
| 13 |
+
"keywords": [],
|
| 14 |
+
"author": "Franek_Le",
|
| 15 |
+
"license": "MIT",
|
| 16 |
+
"description": "",
|
| 17 |
+
"dependencies": {
|
| 18 |
+
"express": "^5.2.1",
|
| 19 |
+
"mineflayer": "^4.25.0",
|
| 20 |
+
"tslib": "^2.8.1"
|
| 21 |
+
},
|
| 22 |
+
"devDependencies": {
|
| 23 |
+
"@eslint/js": "^9.39.4",
|
| 24 |
+
"@types/express": "^5.0.6",
|
| 25 |
+
"@types/node": "^25.9.3",
|
| 26 |
+
"eslint": "^9.39.4",
|
| 27 |
+
"eslint-config-prettier": "^10.1.8",
|
| 28 |
+
"globals": "^17.6.0",
|
| 29 |
+
"prettier": "^3.8.4",
|
| 30 |
+
"typescript": "^6.0.3",
|
| 31 |
+
"typescript-eslint": "^8.61.1"
|
| 32 |
+
}
|
| 33 |
+
}
|
src/bot/bot.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mineflayer, { BotOptions, Bot as MineflayerBot } from "mineflayer";
|
| 2 |
+
import { Config } from "../config/config";
|
| 3 |
+
import { Logger } from "../logger/logger";
|
| 4 |
+
|
| 5 |
+
export class Bot {
|
| 6 |
+
private bot: MineflayerBot | undefined = undefined;
|
| 7 |
+
private readonly botArgs: BotOptions;
|
| 8 |
+
private readonly config: Config;
|
| 9 |
+
private readonly logger: Logger;
|
| 10 |
+
|
| 11 |
+
constructor(config: Config, logger: Logger) {
|
| 12 |
+
this.config = config;
|
| 13 |
+
this.logger = logger;
|
| 14 |
+
|
| 15 |
+
this.botArgs = {
|
| 16 |
+
username: "AFKBot",
|
| 17 |
+
auth: "offline",
|
| 18 |
+
version: this.config.get<string>("minecraft_version"),
|
| 19 |
+
chatLengthLimit: 512,
|
| 20 |
+
host: this.config.get<string>("minecraft_host"),
|
| 21 |
+
port: this.config.get<number>("minecraft_port"),
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
public connect() {
|
| 26 |
+
this.initBot();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
private initBot() {
|
| 30 |
+
this.bot = mineflayer.createBot(this.botArgs);
|
| 31 |
+
|
| 32 |
+
this.bot.on("login", () => {
|
| 33 |
+
this.logger.info(`[Minecraft Bot]: Connected to: ${this.botArgs.host}:${this.botArgs.port}`);
|
| 34 |
+
})
|
| 35 |
+
|
| 36 |
+
this.bot.on("end", () => {
|
| 37 |
+
this.logger.info("[Minecraft Bot]: Disconnected.")
|
| 38 |
+
|
| 39 |
+
this.logger.info("[Minecraft Bot]: Reconnecting...")
|
| 40 |
+
setTimeout(this.initBot, 5000);
|
| 41 |
+
})
|
| 42 |
+
|
| 43 |
+
this.bot.on("kicked", (reason: string, _loggedIn: boolean) => {
|
| 44 |
+
this.logger.warn(`[Minecraft Bot]: Kicked for ${reason}`);
|
| 45 |
+
|
| 46 |
+
this.logger.info("[Minecraft Bot]: Reconnecting...")
|
| 47 |
+
setTimeout(this.initBot, 5000);
|
| 48 |
+
})
|
| 49 |
+
|
| 50 |
+
this.bot.on("error", (error: unknown) => {
|
| 51 |
+
if (typeof error === "object" && error !== null && "code" in error && (error as { code: string }).code === "ECONNREFUSED") {
|
| 52 |
+
this.logger.error(`[Minecraft Bot]: Failed to connect to: ${this.botArgs.host}:${this.botArgs.port}`);
|
| 53 |
+
} else {
|
| 54 |
+
this.logger.error(`[Minecraft Bot]: Unhandled error: ${error}`);
|
| 55 |
+
}
|
| 56 |
+
})
|
| 57 |
+
}
|
| 58 |
+
}
|
src/config/config.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as fs from "fs";
|
| 2 |
+
import * as path from "path";
|
| 3 |
+
|
| 4 |
+
export class Config {
|
| 5 |
+
private readonly data: Record<string, string | object | number | []>;
|
| 6 |
+
|
| 7 |
+
constructor(data: Record<string, any>) {
|
| 8 |
+
this.data = data;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
public static load(fileName = "config.json"): Config {
|
| 12 |
+
const configPath = Config.findConfigFile(fileName);
|
| 13 |
+
|
| 14 |
+
if (!configPath) {
|
| 15 |
+
throw new Error(`Could not find ${fileName}`);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
const contents = fs.readFileSync(configPath, "utf8");
|
| 19 |
+
const data = JSON.parse(contents);
|
| 20 |
+
|
| 21 |
+
return new Config(data);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
public get<T>(key: string): T {
|
| 25 |
+
if (!(key in this.data)) {
|
| 26 |
+
throw new Error(`Missing configuration key: ${key}`);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
return this.data[key] as T;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
public has(key: string): boolean {
|
| 33 |
+
return key in this.data;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
private static findConfigFile(fileName: string): string | null {
|
| 37 |
+
let currentDir = process.cwd();
|
| 38 |
+
|
| 39 |
+
while (true) {
|
| 40 |
+
const candidate = path.join(currentDir, fileName);
|
| 41 |
+
|
| 42 |
+
if (fs.existsSync(candidate)) {
|
| 43 |
+
return candidate;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
const parentDir = path.dirname(currentDir);
|
| 47 |
+
|
| 48 |
+
if (parentDir === currentDir) {
|
| 49 |
+
return null;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
currentDir = parentDir;
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
}
|
src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Server } from "./server/server";
|
| 2 |
+
import { Logger } from "./logger/logger";
|
| 3 |
+
import { Config } from "./config/config";
|
| 4 |
+
import { Bot } from "./bot/bot";
|
| 5 |
+
|
| 6 |
+
function main(): void {
|
| 7 |
+
const config: Config = new Config({});
|
| 8 |
+
const logger: Logger = new Logger();
|
| 9 |
+
|
| 10 |
+
const server: Server = new Server(logger);
|
| 11 |
+
const bot: Bot = new Bot(config, logger);
|
| 12 |
+
|
| 13 |
+
server.get("/", () => {
|
| 14 |
+
return "<h1>NSMP Server</h1>";
|
| 15 |
+
})
|
| 16 |
+
|
| 17 |
+
//bot.connect();
|
| 18 |
+
server.start(7860);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
setImmediate(main);
|
src/logger/logger.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
enum Color {
|
| 4 |
+
Reset = "\x1b[0m",
|
| 5 |
+
Gray = "\x1b[90m",
|
| 6 |
+
Blue = "\x1b[34m",
|
| 7 |
+
Yellow = "\x1b[33m",
|
| 8 |
+
Red = "\x1b[31m",
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export class Logger {
|
| 12 |
+
constructor(private readonly context?: string) {}
|
| 13 |
+
|
| 14 |
+
debug(message: string, ...meta: unknown[]): void {
|
| 15 |
+
this.log("DEBUG", Color.Gray, message, meta);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
info(message: string, ...meta: unknown[]): void {
|
| 19 |
+
this.log("INFO ", Color.Blue, message, meta);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
warn(message: string, ...meta: unknown[]): void {
|
| 23 |
+
this.log("WARN ", Color.Yellow, message, meta);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
error(message: string, ...meta: unknown[]): void {
|
| 27 |
+
this.log("ERROR", Color.Red, message, meta);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
private log(level: string, color: Color, message: string, meta: unknown[]): void {
|
| 31 |
+
const timestamp = new Date().toISOString();
|
| 32 |
+
const context = this.context ? ` [${this.context}]` : "";
|
| 33 |
+
|
| 34 |
+
console.log(
|
| 35 |
+
`${color}[${timestamp}] ${level}${context}: ${message}${Color.Reset}`,
|
| 36 |
+
...meta
|
| 37 |
+
);
|
| 38 |
+
}
|
| 39 |
+
}
|
src/server/server.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import express, {Application, RequestHandler} from "express";
|
| 2 |
+
import { Server as HttpServer } from "http";
|
| 3 |
+
import { Logger } from "../logger/logger";
|
| 4 |
+
|
| 5 |
+
export class Server {
|
| 6 |
+
private readonly app: Application;
|
| 7 |
+
private server?: HttpServer;
|
| 8 |
+
private readonly logger: Logger;
|
| 9 |
+
|
| 10 |
+
constructor(logger: Logger) {
|
| 11 |
+
this.logger = logger;
|
| 12 |
+
|
| 13 |
+
this.app = express();
|
| 14 |
+
|
| 15 |
+
this.app.use(express.json());
|
| 16 |
+
this.app.use(express.urlencoded({ extended: true }));
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
public start(port: number): Promise<void> {
|
| 20 |
+
return new Promise((resolve, reject) => {
|
| 21 |
+
if (this.server) {
|
| 22 |
+
return reject(new Error("Server is already running"));
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
this.server = this.app.listen(port, () => {
|
| 26 |
+
this.logger.info(`[Server]: Listening on port ${port}`);
|
| 27 |
+
resolve();
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
this.server.on("error", reject);
|
| 31 |
+
});
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
public stop(): Promise<void> {
|
| 35 |
+
return new Promise((resolve, reject) => {
|
| 36 |
+
if (!this.server) {
|
| 37 |
+
return resolve();
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
this.server.close((err) => {
|
| 41 |
+
if (err) {
|
| 42 |
+
return reject(err);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
this.server = undefined;
|
| 46 |
+
resolve();
|
| 47 |
+
});
|
| 48 |
+
});
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
public get(path: string, handler: RequestHandler): void {
|
| 52 |
+
this.app.get(path, handler);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
public post(path: string, handler: RequestHandler): void {
|
| 56 |
+
this.app.post(path, handler);
|
| 57 |
+
}
|
| 58 |
+
}
|
tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"module": "NodeNext",
|
| 5 |
+
"moduleResolution": "NodeNext",
|
| 6 |
+
|
| 7 |
+
"rootDir": "./src",
|
| 8 |
+
"outDir": "./dist",
|
| 9 |
+
|
| 10 |
+
"strict": true,
|
| 11 |
+
|
| 12 |
+
"noImplicitAny": true,
|
| 13 |
+
"noUnusedLocals": true,
|
| 14 |
+
"noUnusedParameters": true,
|
| 15 |
+
"noImplicitReturns": true,
|
| 16 |
+
"noFallthroughCasesInSwitch": true,
|
| 17 |
+
|
| 18 |
+
"sourceMap": true,
|
| 19 |
+
"declaration": true,
|
| 20 |
+
|
| 21 |
+
"esModuleInterop": true,
|
| 22 |
+
"forceConsistentCasingInFileNames": true,
|
| 23 |
+
"skipLibCheck": true
|
| 24 |
+
},
|
| 25 |
+
|
| 26 |
+
"include": [
|
| 27 |
+
"src/**/*"
|
| 28 |
+
],
|
| 29 |
+
|
| 30 |
+
"exclude": [
|
| 31 |
+
"node_modules",
|
| 32 |
+
"dist"
|
| 33 |
+
]
|
| 34 |
+
}
|