Files
ts3gotify/src/ts3gotify.ts
T
t.behrendt d6d9c63b3d
CD / Test (push) Successful in 19s
CD / Create tag (push) Successful in 18s
CD / Build and push (amd64) (push) Successful in 34s
CD / Build and push (arm64) (push) Successful in 1m38s
CD / Create manifest (push) Successful in 8s
fix: disconnection issue by adding a persistent ping (#121)
Fixes the frequent disconnection issue, by adding a ping.
The "version" call works without authentication and is lightweight. Passing the "true" flag, prevents delivery of cached results.
This keeps the socket always connected. Feature may be removed in the future if the upstream library has a proper fix for the issue.

Reviewed-on: #121
Co-authored-by: Timo Behrendt <t.behrendt@t00n.de>
Co-committed-by: Timo Behrendt <t.behrendt@t00n.de>
2026-05-09 14:38:52 +02:00

129 lines
3.1 KiB
TypeScript

import type { Gotify } from "gotify";
import type { Logger } from "pino";
import type { TeamSpeak, TextMessageTargetMode } from "ts3-nodejs-library";
import {
ClientConnect,
ClientDisconnect,
ClientMoved,
TextMessage,
} from "ts3-nodejs-library/lib/types/Events";
import type { GotifyConfig, Mode } from "./types";
function resolveMessageTarget(target: TextMessageTargetMode): string {
if (target === 1) {
return "Client";
} else if (target === 2) {
return "Channel";
} else {
return "Server";
}
}
export function getModes(mode: Mode[]): {
[key in Mode]: boolean;
} {
const modes = mode
.map((mode) => {
return { [mode]: true };
})
.reduce((acc, cur) => {
return { ...acc, ...cur };
});
return {
connect: false,
disconnect: false,
moved: false,
message: false,
...modes,
};
}
export function ts3gotifyFactory(
ts3Client: TeamSpeak,
gotifyClient: Gotify,
gotifyConfig: GotifyConfig,
logger: Logger,
pingInterval = 2_500
) {
let reconnectInProgress = false;
function sendNotification(message: string) {
gotifyClient
.send({
...gotifyConfig,
message: message,
})
.catch((error: Error) => {
logger.error(`Error sending message to gotify: ${error.message}`);
});
}
function registerEventListenerForMode(mode: Mode) {
switch (mode) {
case "connect":
ts3Client.on("clientconnect", (event: ClientConnect) =>
sendNotification(`${event.client.nickname} connected`)
);
break;
case "disconnect":
ts3Client.on("clientdisconnect", (event: ClientDisconnect) =>
sendNotification(`${event.client?.nickname} disconnected`)
);
break;
case "moved":
ts3Client.on("clientmoved", (event: ClientMoved) =>
sendNotification(
`${event.client.nickname} got moved to ${event.channel.name}`
)
);
break;
case "message":
ts3Client.on("textmessage", (event: TextMessage) =>
sendNotification(
`${event.invoker.nickname} wrote ${
event.msg
} to a ${resolveMessageTarget(event.targetmode)}`
)
);
break;
}
}
async function reconnectWithBackoff() {
if (reconnectInProgress) {
return;
}
reconnectInProgress = true;
logger.info("disconnected, trying to reconnect...");
try {
await ts3Client.reconnect(10, 2000);
logger.info("reconnected!");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error(`Error reconnecting to TS3 server: ${message}`);
} finally {
reconnectInProgress = false;
}
}
setInterval(() => ts3Client.version(true), pingInterval);
ts3Client.on("close", async () => {
await reconnectWithBackoff();
});
ts3Client.on("error", (error: Error) => {
logger.error(`Error connecting to TS3 server: ${error.message}`);
reconnectWithBackoff();
});
return {
registerEventListenerForMode,
};
}
export type Ts3Gotify = ReturnType<typeof ts3gotifyFactory>;