Compare commits

..

No commits in common. "d24929da0cb868772cc92c79582ecfbbc52944da" and "742a46c708bee585e55e315764c337c98620db4e" have entirely different histories.

2 changed files with 56 additions and 51 deletions

View file

@ -7,17 +7,18 @@ const opcodes = {
EVENT: 0, EVENT: 0,
CLIENT_HEARTBEAT: 1, CLIENT_HEARTBEAT: 1,
IDENTIFY: 2, IDENTIFY: 2,
RESUME: 6,
INVALID_SESSION: 9, INVALID_SESSION: 9,
HELLO: 10, HELLO: 10,
HEARTBEAT_ACK: 11, HEARTBEAT_ACK: 11,
}; };
const skipReconnectFor = [ const reconnectOnCloseCodes = [
4004, 4010, 4011, 4012, 4013, 4014 1000, 1001, 4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009
]; ];
class DiscordClient extends EventEmitter { class DiscordClient extends EventEmitter {
constructor(token, { intents, gatewayUrl="wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream", apiBase="https://discord.com/api/v9" } = {}) { constructor(token, { intents, baseDomain="discord.com", gatewayUrl="wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream", apiBase="https://discord.com/api/v9" } = {}) {
super(); super();
this.token = token; this.token = token;
@ -78,11 +79,19 @@ class DiscordClient extends EventEmitter {
}; };
} }
_getResumePayload() {
return {
token: this.token,
session_id: this.sessionId,
seq: this.seq
};
}
_handleGatewayMessage(ws, message) { _handleGatewayMessage(ws, message) {
try { try {
message = JSON.parse(message); message = JSON.parse(message);
} catch(e) { } catch(e) {
console.error("error: DiscordClient: on 'message': failed to parse incoming message as JSON", e); console.error("error: DiscordClient: on `message`: failed to parse incoming message as JSON", e);
return; return;
} }
@ -94,20 +103,28 @@ class DiscordClient extends EventEmitter {
switch (message.op) { switch (message.op) {
case opcodes.HELLO: { case opcodes.HELLO: {
console.log(`DiscordClient: HELLO; heartbeat_interval=${payload.heartbeat_interval}`);
this._setHeartbeat(payload.heartbeat_interval); this._setHeartbeat(payload.heartbeat_interval);
ws.send(JSON.stringify({ if (this.resuming) {
op: opcodes.IDENTIFY, console.warn("DiscordClient: resuming...");
d: this._getIdentifyPayload() this.resuming = false;
})); ws.send(JSON.stringify({
op: opcodes.RESUME,
d: this._getResumePayload()
}));
} else {
ws.send(JSON.stringify({
op: opcodes.IDENTIFY,
d: this._getIdentifyPayload()
}));
}
break; break;
} }
case opcodes.EVENT: { case opcodes.EVENT: {
switch (message.t) { switch (message.t) {
case "READY": { case "READY": {
console.log("DiscordClient: READY"); console.log("DiscordClient: ready");
this.user = payload.user; this.user = payload.user;
this.sessionId = payload.session_id; this.sessionId = payload.session_id;
this.guilds = payload.guilds; this.guilds = payload.guilds;
@ -194,8 +211,19 @@ class DiscordClient extends EventEmitter {
} }
case opcodes.INVALID_SESSION: { case opcodes.INVALID_SESSION: {
console.error("DiscordClient: INVALID_SESSION - please check your authentication token"); if (message.d) {
console.error("DiscordClient: INVALID_SESSION: will not reconnect"); // connection is resumable, we are going to resume the connection
this.resuming = true;
this.connect();
} else {
// connection is not resumable, wait some time and then send a new IDENTIFY payload
setTimeout(() => {
ws.send(JSON.stringify({
op: opcodes.IDENTIFY,
d: this._getIdentifyPayload()
}));
}, 3500);
}
break; break;
} }
@ -207,9 +235,7 @@ class DiscordClient extends EventEmitter {
} }
connect() { connect() {
console.log("DiscordClient: connecting...");
if (this.ws) { if (this.ws) {
console.log("DiscordClient: a websocket connection already exists, killing...");
this.ws.removeAllListeners(); this.ws.removeAllListeners();
this.ws.close(); this.ws.close();
this.ws = null; this.ws = null;
@ -223,37 +249,30 @@ class DiscordClient extends EventEmitter {
}); });
// we decompressed the data, send it to the handler now // we decompressed the data, send it to the handler now
this.inflate.on("data", (message) => this.inflate.on("data", (message) => this._handleGatewayMessage(ws, message));
this._handleGatewayMessage(ws, message)
);
ws.on("message", (data, isBinary) => { ws.on("message", (data, isBinary) => {
// pass the data to the decompressor // pass the data to the decompressor
this.inflate.write(data); this.inflate.write(data);
}); });
ws.on("open", () => {
console.log("DiscordClient: WebSocket 'open'")
});
ws.on("close", (code, reason) => { ws.on("close", (code, reason) => {
reason = reason.toString(); reason = reason.toString();
console.error(`DiscordClient: on 'close': disconnected from gateway: code '${code}', reason '${reason}'`); console.error(`DiscordClient: on \`close\`: disconnected from gateway: code \`${code}\`, reason \`${reason}\``);
this.emit("close", code, reason); this.emit("close", code, reason);
this._setHeartbeat(-1); this._setHeartbeat(-1);
if (skipReconnectFor.includes(code)) { if (reconnectOnCloseCodes.includes(code)) {
console.error("DiscordClient: on 'close': the exit code above is in skipReconnectFor, and thus the server will not reconnect."); this.resuming = true;
} else {
console.log("DiscordClient: on 'close': the client will now attempt to reconnect...");
this.connect(); this.connect();
} }
}); });
ws.on("error", (e) => { ws.on("error", (e) => {
console.error("DiscordClient: on 'error': websocket error:", e); console.error("DiscordClient: websocket error:", e);
console.log("DiscordClient: on 'error': reconnecting due to previous websocket error..."); console.log("DiscordClient: reconnecting?");
this._setHeartbeat(-1); this._setHeartbeat(-1);
this.resuming = true;
this.connect(); this.connect();
}); });
} }

View file

@ -23,8 +23,6 @@ const messageTypes = {
}; };
const chatMessageRegex = /^\[(?:.*?)\]: \<(?<username>.*)\> (?<message>.*)/; const chatMessageRegex = /^\[(?:.*?)\]: \<(?<username>.*)\> (?<message>.*)/;
const joinNotificationRegex = /^\[(?:.*?)\]: (?<username>.*) joined the game/;
const leaveNotificationRegex = /^\[(?:.*?)\]: (?<username>.*) left the game/;
const rconConnection = new Rcon("localhost", "25575", process.env.RCON_PASSWORD); const rconConnection = new Rcon("localhost", "25575", process.env.RCON_PASSWORD);
export default class GatewayClient { export default class GatewayClient {
@ -93,7 +91,7 @@ export default class GatewayClient {
console.log("gateway: open"); console.log("gateway: open");
}); });
this.ws.on("close", () => { this.ws.on("close", () => {
console.log("gateway: closed, reconnecting in 4000ms"); console.log("gateway: closed");
setTimeout(() => { setTimeout(() => {
console.log("gateway: reconnecting"); console.log("gateway: reconnecting");
this.connect(token); this.connect(token);
@ -143,10 +141,10 @@ async function main() {
rconConnection.on("error", (e) => { rconConnection.on("error", (e) => {
console.error("rcon: got error", e); console.error("rcon: got error", e);
if (!rconConnection.hasAuthed) { if (!rconConnection.hasAuthed) {
console.log("rcon: reconnecting in 5000ms due to error before hasAuthed (server might not be up yet?)"); console.log("rcon: reconnecting in 1200ms due to error before hasAuthed");
setTimeout(() => { setTimeout(() => {
rconConnection.connect(); rconConnection.connect();
}, 5000); }, 1200);
} }
}); });
const gateway = new GatewayClient(GATEWAY_ORIGIN); const gateway = new GatewayClient(GATEWAY_ORIGIN);
@ -162,24 +160,12 @@ async function main() {
process.stdin.on("data", async (rawDataBuffer) => { process.stdin.on("data", async (rawDataBuffer) => {
const stringData = rawDataBuffer.toString().trim(); const stringData = rawDataBuffer.toString().trim();
console.log(stringData); console.log(stringData);
const result = chatMessageRegex.exec(stringData);
const joinResult = joinNotificationRegex.exec(stringData); if (!result)
if (joinResult) {
await sendBridgeMessageAs(TARGET_GUILD_ID, TARGET_CHANNEL_ID, `**${joinResult.groups.username}** joined the game`, null, null);
return; return;
} const { username, message } = result.groups;
const leaveResult = leaveNotificationRegex.exec(stringData); await sendBridgeMessageAs(TARGET_GUILD_ID, TARGET_CHANNEL_ID, message, username, null);
if (leaveResult) {
await sendBridgeMessageAs(TARGET_GUILD_ID, TARGET_CHANNEL_ID, `**${leaveResult.groups.username}** left the game`, null, null);
return;
}
const messageResult = chatMessageRegex.exec(stringData);
if (messageResult) {
await sendBridgeMessageAs(TARGET_GUILD_ID, TARGET_CHANNEL_ID, messageResult.groups.message, messageResult.groups.username, null);
return;
}
}); });
} }