Skip to content

Commit 7299ae6

Browse files
committed
Implement events and command modules
1 parent f6f1beb commit 7299ae6

File tree

6 files changed

+302
-11
lines changed

6 files changed

+302
-11
lines changed

command.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Config } from "./config.ts";
2+
import { JavaServer, JavaServerEvents } from "./java_server.ts";
3+
import get from "https://deno.land/x/[email protected]/src/object/get.ts";
4+
import { useEvents } from "./events.ts";
5+
6+
interface CommandEvent {
7+
player: string;
8+
command: string;
9+
args: string[];
10+
timestamp: number;
11+
}
12+
13+
type CommandCallback = (event: CommandEvent) => void;
14+
15+
export interface CommandConfig {
16+
initialized: boolean;
17+
prefix: string;
18+
commands: {
19+
[cmd: string]: CommandCallback[];
20+
};
21+
commandFunc?: (cmd: string, callback: CommandCallback) => void;
22+
}
23+
24+
declare module "./config.ts" {
25+
export interface Config {
26+
command: CommandConfig;
27+
}
28+
}
29+
30+
declare module "./java_server.ts" {
31+
export interface JavaServerEvents {
32+
command: CommandCallback;
33+
}
34+
}
35+
36+
const DEFAULT_CONFIG: CommandConfig = {
37+
initialized: false,
38+
prefix: "~",
39+
commands: {},
40+
};
41+
42+
export function useCommand(javaServer: JavaServer) {
43+
if (javaServer.config?.command?.initialized) {
44+
return javaServer.config.command.commandFunc!;
45+
}
46+
47+
useEvents(javaServer);
48+
49+
// @ts-ignore
50+
javaServer.config = { command: DEFAULT_CONFIG, ...javaServer.config };
51+
javaServer.config.command.initialized = true;
52+
53+
const regex = new RegExp(
54+
`^${javaServer.config.command.prefix}([\\w]+)\\s?(.*)`,
55+
"i"
56+
);
57+
58+
javaServer.on("chat", (event) => {
59+
const stripped = event.message.match(regex);
60+
if (stripped) {
61+
const command = stripped[1].toLowerCase();
62+
javaServer.emit("command", {
63+
player: event.player,
64+
command,
65+
args: stripped[2].split(" "),
66+
timestamp: Date.now(),
67+
});
68+
}
69+
});
70+
71+
javaServer.on("command", (event) => {
72+
if (javaServer.config.command.commands.hasOwnProperty(event.command)) {
73+
javaServer.config.command.commands[event.command].forEach((callback) => {
74+
Promise.resolve()
75+
.then(() => callback(event))
76+
.catch((e) => console.error(e));
77+
});
78+
}
79+
});
80+
81+
javaServer.config.command.commandFunc = (
82+
cmd: string,
83+
callback: CommandCallback
84+
) => {
85+
cmd = cmd.toLowerCase();
86+
javaServer.config.command.commands[cmd] =
87+
javaServer.config.command.commands[cmd] || [];
88+
javaServer.config.command.commands[cmd].push(callback);
89+
};
90+
91+
return javaServer.config.command.commandFunc;
92+
}

events.ts

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { Config } from "./config.ts";
2+
import { JavaServer, JavaServerEvents } from "./java_server.ts";
3+
import get from "https://deno.land/x/[email protected]/src/object/get.ts";
4+
5+
export interface EventsConfig {
6+
initialized: boolean;
7+
flavorSpecific: {
8+
default: {
9+
parseChatEvent: (consoleOutput: string) => {
10+
player: string;
11+
message: string;
12+
} | void;
13+
parseLoginEvent: (consoleOutput: string) => {
14+
player: string;
15+
ip: string;
16+
} | void;
17+
parseLogoutEvent: (consoleOutput: string) => {
18+
player: string;
19+
reason: string;
20+
} | void;
21+
parseAchievementEvent: (consoleOutput: string) => {
22+
player: string;
23+
achievement: string;
24+
} | void;
25+
parseStartEvent: (consoleOutput: string) => {} | void;
26+
parseStopEvent: (consoleOutput: string) => {} | void;
27+
};
28+
};
29+
}
30+
31+
declare module "./config.ts" {
32+
export interface Config {
33+
events: EventsConfig;
34+
}
35+
}
36+
37+
declare module "./java_server.ts" {
38+
export interface JavaServerEvents {
39+
chat: (event: {
40+
player: string;
41+
message: string;
42+
timestamp: number;
43+
}) => void;
44+
login: (event: { player: string; ip: string; timestamp: number }) => void;
45+
logout: (event: {
46+
player: string;
47+
reason: string;
48+
timestamp: number;
49+
}) => void;
50+
achievement: (event: {
51+
player: string;
52+
achievement: string;
53+
timestamp: number;
54+
}) => void;
55+
start: (event: { timestamp: number }) => void;
56+
stop: (event: { timestamp: number }) => void;
57+
}
58+
}
59+
60+
const DEFAULT_CONFIG: EventsConfig = {
61+
initialized: false,
62+
flavorSpecific: {
63+
default: {
64+
parseChatEvent(consoleOutput) {
65+
const parsed = consoleOutput.match(
66+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: <(\w+)> (.*)/i
67+
);
68+
if (parsed) {
69+
return {
70+
player: parsed[1] as string,
71+
message: parsed[2] as string,
72+
};
73+
}
74+
},
75+
parseLoginEvent(string) {
76+
const parsed = string.match(
77+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: (\w+)\[\/([\d.:]+)\] logged in/
78+
);
79+
if (parsed) {
80+
return {
81+
player: parsed[1],
82+
ip: parsed[2],
83+
};
84+
}
85+
},
86+
parseLogoutEvent(string) {
87+
const parsed = string.match(
88+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: (\w+) lost connection: (.+)/
89+
);
90+
if (parsed) {
91+
return {
92+
player: parsed[1],
93+
reason: parsed[2],
94+
};
95+
}
96+
},
97+
parseAchievementEvent(string) {
98+
const parsed = string.match(
99+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: (\w+) has made the advancement \[([\w\s]+)\]/
100+
);
101+
if (parsed) {
102+
return {
103+
player: parsed[1],
104+
achievement: parsed[2],
105+
};
106+
}
107+
},
108+
parseStartEvent(string) {
109+
const parsed = string.match(
110+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: Done/
111+
);
112+
if (parsed) {
113+
return {};
114+
}
115+
},
116+
parseStopEvent(string) {
117+
const parsed = string.match(
118+
/^\[[\d:]{8}\] \[Server thread\/INFO\]: Stopping server/
119+
);
120+
if (parsed) {
121+
return {};
122+
}
123+
},
124+
},
125+
},
126+
};
127+
128+
const EVENTS_MAP = [
129+
["chat", "parseChatEvent"],
130+
["login", "parseLoginEvent"],
131+
["logout", "parseLogoutEvent"],
132+
["achievement", "parseAchievementEvent"],
133+
["start", "parseStartEvent"],
134+
["stop", "parseStopEvent"],
135+
] as const;
136+
137+
export function useEvents(javaServer: JavaServer) {
138+
if (javaServer.config?.events?.initialized) {
139+
return;
140+
}
141+
142+
// @ts-ignore
143+
javaServer.config = { events: DEFAULT_CONFIG, ...javaServer.config };
144+
javaServer.config.events.initialized = true;
145+
146+
javaServer.on("console", (consoleLine) => {
147+
const result = EVENTS_MAP.reduce<null | {
148+
event: keyof JavaServerEvents;
149+
payload: any;
150+
}>((acc, event) => {
151+
if (acc) return acc;
152+
153+
const parseEvent = get(
154+
javaServer.config.events,
155+
`flavorSpecific.${javaServer.config.flavor}.${event[1]}`,
156+
javaServer.config.events.flavorSpecific.default[event[1]]
157+
);
158+
const matches = parseEvent(consoleLine);
159+
if (matches) return { event: event[0], payload: matches };
160+
161+
return null;
162+
}, null);
163+
164+
if (result) {
165+
result.payload.timestamp = Date.now();
166+
javaServer.emit(result.event, result.payload);
167+
}
168+
});
169+
}

java_server.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ declare module "./config.ts" {
1616
}
1717
}
1818

19-
const DEFAULT_CONFIG = {
19+
export interface JavaServerEvents {
20+
console: (consoleLine: string) => void;
21+
}
22+
23+
const DEFAULT_CONFIG: JavaServerConfig = {
2024
jar: "server.jar",
2125
args: ["-Xmx1024M", "-Xms1024M"],
2226
path: "./server",
@@ -27,7 +31,18 @@ const DEFAULT_CONFIG = {
2731
const textDecoder = new TextDecoder();
2832

2933
export class JavaServer extends EventEmitter {
30-
private config: Config;
34+
// @ts-expect-error
35+
on<U extends keyof JavaServerEvents>(
36+
event: U,
37+
listener: JavaServerEvents[U]
38+
): this;
39+
40+
// @ts-expect-error
41+
emit<U extends keyof JavaServerEvents>(
42+
event: U,
43+
...args: Parameters<JavaServerEvents[U]>
44+
): boolean;
45+
public config: Config;
3146
private process?: Deno.Process;
3247

3348
constructor(config: Partial<Config> = {}) {

mod.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
import { ScriptServer } from "./script_server.ts";
2+
import { useEvents } from "./events.ts";
3+
import { useCommand } from "./command.ts";
24

3-
const scriptServer = new ScriptServer();
5+
(() => {
6+
try {
7+
const scriptServer = new ScriptServer();
48

5-
scriptServer.start();
9+
useEvents(scriptServer.javaServer);
10+
const command = useCommand(scriptServer.javaServer);
611

7-
scriptServer.on("start", async () => {
8-
console.log(await scriptServer.send("help"));
9-
});
12+
scriptServer.javaServer.on("command", (event) => {
13+
console.log("command", event);
14+
});
15+
16+
command("head", (event) => {
17+
console.log("head", event);
18+
});
19+
20+
scriptServer.start();
21+
} catch (error) {
22+
console.error(error);
23+
}
24+
})();

rcon_connection.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ declare module "./config.ts" {
1616
}
1717
}
1818

19-
const DEFAULT_CONFIG = {
19+
const DEFAULT_CONFIG: RconConnectionConfig = {
2020
host: "localhost",
2121
rconPort: 25575,
2222
rconPassword: "0000",
2323
rconBuffer: 100,
2424
};
2525

2626
export class RconConnection extends EventEmitter {
27-
private config: Config;
27+
public config: Config;
2828

2929
private authenticated = false;
3030
private queue: [string, (value: string) => void][] = [];

script_server.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ declare module "./config.ts" {
1818
}
1919
}
2020

21-
const DEFAULT_CONFIG = {
21+
const DEFAULT_CONFIG: ScriptServerConfig = {
2222
flavorSpecific: {
2323
default: {
2424
rconRunningRegExp: /^\[[\d:]{8}\] \[Server thread\/INFO\]: RCON running/i,
@@ -29,7 +29,7 @@ const DEFAULT_CONFIG = {
2929
export class ScriptServer extends EventEmitter {
3030
public javaServer: JavaServer;
3131
public rconConnection: RconConnection;
32-
private config: Config;
32+
public config: Config;
3333

3434
constructor(config: Partial<Config> = {}) {
3535
super();

0 commit comments

Comments
 (0)