From ca114f677eeb20ad7de9e2802b9a4fb448661bd8 Mon Sep 17 00:00:00 2001
From: Flummi <git@srv.fail>
Date: Tue, 18 Mar 2025 10:47:58 +0100
Subject: [PATCH] refactor: enhance event handling in IRC, Slack, and Telegram
 clients

---
 src/clients/irc.ts   | 56 ++++++++++++++++++++++++--------------------
 src/clients/slack.ts | 36 +++++++++++++++++++++-------
 src/clients/tg.ts    | 13 ++++++++++
 src/types.d.ts       |  1 -
 4 files changed, 70 insertions(+), 36 deletions(-)

diff --git a/src/clients/irc.ts b/src/clients/irc.ts
index 5f027db..e64b6ff 100644
--- a/src/clients/irc.ts
+++ b/src/clients/irc.ts
@@ -29,6 +29,11 @@ interface ParsedCommand {
   params: string[];
 }
 
+interface IRCEvents {
+  data: [string | [string, any]];
+  error: [string];
+}
+
 const colors = {
   white: "00", black: "01", navy: "02", green: "03", red: "04",
   brown: "05", purple: "06", orange: "07", yellow: "08",
@@ -59,6 +64,14 @@ export default class irc extends EventEmitter {
     user: Map<string, { account?: string; cached: number }>;
   };
 
+  emit<K extends keyof IRCEvents>(event: K, ...args: IRCEvents[K]): boolean {
+    return super.emit(event, ...args);
+  }
+
+  on<K extends keyof IRCEvents>(event: K, listener: (...args: IRCEvents[K]) => void): this {
+    return super.on(event, listener);
+  }
+
   constructor(options: IRCOptions) {
     super();
     this.options = {
@@ -102,38 +115,29 @@ export default class irc extends EventEmitter {
     this.connect();
   }
 
-  connect(reconnect: boolean = false): void {
-    if(reconnect)
-      this.socket = undefined;
-  
-    if(this.options.ssl) {
-      this.socket = tls.connect({
+  private createSocket(): net.Socket | tls.TLSSocket {
+    return this.options.ssl
+      ? tls.connect({
         host: this.options.host,
         port: this.options.port,
         rejectUnauthorized: !this.options.selfSigned,
-      }, () => this.handleConnection());
-    }
-    else {
-      this.socket = net.connect({
+      })
+      : net.connect({
         host: this.options.host,
         port: this.options.port,
-      }, () => this.handleConnection());
-    }
-  
-    if(!this.socket)
-      throw new Error("Socket konnte nicht initialisiert werden.");
-  
+      });
+  }
+
+  connect(reconnect: boolean = false): void {
+    if(reconnect)
+      this.socket = undefined;
+
+    this.socket = this.createSocket();
+    this.socket.on("data", (msg: string) => this.handleData(msg));
+    this.socket.on("end", () => this.handleDisconnect());
+    this.socket.on("error", (err: Error) => this.handleError(err));
     this.socket.setEncoding("utf-8");
-    this.socket.on("data", (msg: string) => {
-      console.log("Received data:", msg);
-      this.handleData(msg);
-    });
-    this.socket.on("end", () => {
-      this.handleDisconnect();
-    });
-    this.socket.on("error", (err: Error) => {
-      this.handleError(err);
-    });
+    this.handleConnection();
   }
   
   private handleConnection(): void {
diff --git a/src/clients/slack.ts b/src/clients/slack.ts
index 355f7f6..d0ebd09 100644
--- a/src/clients/slack.ts
+++ b/src/clients/slack.ts
@@ -40,12 +40,27 @@ interface SlackRTMStartResponse {
   description?: string;
 }
 
+interface SlackEvents {
+  data: [string | [string, any]];
+  error: [string];
+  message: [SlackMessage];
+}
+
 export default class slack extends EventEmitter {
   private options: Required<SlackOptions>;
   private token: string;
   private api: string = "https://slack.com/api";
   private interval: NodeJS.Timeout | null = null;
   private server: ServerInfo;
+  private reconnectAttempts = 0;
+
+  emit<K extends keyof SlackEvents>(event: K, ...args: SlackEvents[K]): boolean {
+    return super.emit(event, ...args);
+  }
+
+  on<K extends keyof SlackEvents>(event: K, listener: (...args: SlackEvents[K]) => void): this {
+    return super.on(event, listener);
+  }
 
   constructor(options: SlackOptions) {
     super();
@@ -92,6 +107,7 @@ export default class slack extends EventEmitter {
   
     if(res.url) {
       this.server.wss.url = url.parse(res.url);
+      this.reconnectAttempts = 0;
       this.initializeWebSocket();
     }
     else
@@ -128,7 +144,7 @@ export default class slack extends EventEmitter {
     if(!this.server.wss.socket)
       return;
   
-    this.interval = setInterval(async () => await this.ping(), 30000);
+    this.interval = setInterval(async () => await this.ping(), 3e4);
   
     this.server.wss.socket.on("data", async (data: Buffer) => {
       try {
@@ -158,12 +174,15 @@ export default class slack extends EventEmitter {
   }
 
   async reconnect(): Promise<void> {
-    this.server.wss.url = null;
-    this.server.wss.socket = null;
-    if(this.interval)
-      clearInterval(this.interval);
-    this.emit("data", ["info", "reconnecting slack"]);
-    await this.connect();
+    if(this.reconnectAttempts >= 5) {
+      this.emit("data", ["error", "Too many reconnect attempts"]);
+      return;
+    }
+    this.reconnectAttempts++;
+    setTimeout(async () => {
+      this.emit("data", ["info", "Reconnecting to Slack"]);
+      await this.connect();
+    }, this.reconnectAttempts * 1e3);
   }
 
   async getChannel(channelId: string): Promise<string | undefined> {
@@ -247,8 +266,7 @@ export default class slack extends EventEmitter {
 
   private parseData(data: Buffer): SlackMessage | undefined {
     try {
-      const json = JSON.parse(data.toString());
-      return json;
+      return JSON.parse(data.toString()) as SlackMessage;
     }
     catch(err: any) {
       this.emit("data", ["error", "failed to parse data"]);
diff --git a/src/clients/tg.ts b/src/clients/tg.ts
index cf6ed6b..3ee1f53 100644
--- a/src/clients/tg.ts
+++ b/src/clients/tg.ts
@@ -50,6 +50,11 @@ interface TelegramUpdate {
   inline_query?: any;
 }
 
+interface TelegramEvents {
+  data: [string | [string, any]];
+  error: [string];
+}
+
 export default class tg extends EventEmitter {
   private options: Required<TelegramOptions>;
   private token: string;
@@ -64,6 +69,14 @@ export default class tg extends EventEmitter {
     me: {},
   };
 
+  emit<K extends keyof TelegramEvents>(event: K, ...args: TelegramEvents[K]): boolean {
+    return super.emit(event, ...args);
+  }
+
+  on<K extends keyof TelegramEvents>(event: K, listener: (...args: TelegramEvents[K]) => void): this {
+    return super.on(event, listener);
+  }
+
   constructor(options: TelegramOptions) {
     super();
     this.options = {
diff --git a/src/types.d.ts b/src/types.d.ts
index 890bc15..73ac29b 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -11,7 +11,6 @@ export interface Bot {
   server: {
     channel: Map<string, string[]>;
     motd: string;
-    //user: Map<string, any>;
     user: {
       [channel: string]: any
     };