diff --git a/irc_bot.py b/irc_bot.py index 54d9d51..37bca5a 100644 --- a/irc_bot.py +++ b/irc_bot.py @@ -1,15 +1,16 @@ +import queue +import redis +import requests import socket +import ssl import threading import time -import re -import requests -import ssl from auth_db import auth_db from utils import strip_bbcode, parse_username from config import ( SERVER, PORT, BOTNICK, NICKSERV_USER, NICKSERV_PASS, API_ENDPOINT, API_TOKEN, VERIFY_ENDPOINT, LOBBY_CHANNEL, MAIN_CHANNEL, ADMIN_CHANNEL, STAFF_CHANNELS, - RECONNECT_DELAY, GROUP_NAMES, STAFF_GROUP_IDS, ADMIN_GROUP_IDS, OPER_USER, OPER_PASS + RECONNECT_DELAY, GROUP_NAMES, STAFF_GROUP_IDS, ADMIN_GROUP_IDS, OPER_USER, OPER_PASS, REDIS_PW ) class IRCBot: @@ -18,6 +19,7 @@ class IRCBot: self.nickname = BOTNICK self.running = False self.lock = threading.Lock() + self.send_queue = queue.Queue() # Connect to IRC server, handle nick conflicts, wait for welcome, and join channel def connect(self): @@ -84,8 +86,13 @@ class IRCBot: if self.running: return self.running = True + print("[+] Starting IRC listener thread") threading.Thread(target=self.listen_loop, daemon=True).start() - print("[+] IRC listener started") + print("[+] Starting IRC writer thread") + threading.Thread(target=self.writer_loop, daemon=True).start() + print("[+] Starting Redis subscriber thread") + threading.Thread(target=self.redis_subscribe_loop, daemon=True).start() + print("[+] IRC + Redis bridge fully initialized") def listen_loop(self): while True: @@ -107,6 +114,21 @@ class IRCBot: print(f"[-] IRC error: {e}") self.connect() + def redis_subscribe_loop(self): + r = redis.Redis(host='localhost', port=6969, password=REDIS_PW, db=0) + pubsub = r.pubsub() + pubsub.subscribe("chatbox_to_irc") + print("[REDIS] Subscribed to chatbox_to_irc") + + for message in pubsub.listen(): + if message["type"] == "message": + try: + text = message["data"].decode() + print(f"[REDIS] Got message: {text}") + self.send_to_channel(text) + except Exception as e: + print(f"[-] Redis IRC dispatch error: {e}") + # Handle verification messages from users def handle_message(self, line): user = parse_username(line) @@ -197,25 +219,43 @@ class IRCBot: # Send notice to user def send_notice(self, nick, msg): + if not self.irc or not self.is_connected(): + print("[-] IRC connection is closed or unavailable.") + return try: - self.irc.send(f"NOTICE {nick} :{msg}\r\n".encode("utf-8")) + with self.lock: # Ensure thread-safe socket access + print(f"[QUEUE] Queuing message: {msg.strip()}") + self.send_queue.put(f"NOTICE {nick} :{msg}\r\n") except Exception as e: print(f"[-] Notice failed: {e}") # Send IRC message to channel def send_to_channel(self, msg): + if not self.irc or not self.is_connected(): + print("[-] IRC connection is closed or unavailable.") + return clean = strip_bbcode(msg).replace("\n", " ").replace("\r", " ") try: - self.irc.send(f"PRIVMSG {LOBBY_CHANNEL} :{clean}\r\n".encode("utf-8")) + with self.lock: # Ensure thread-safe socket access + print(f"[QUEUE] Queuing message: {clean.strip()}") + self.send_queue.put(f"PRIVMSG {LOBBY_CHANNEL} :{clean}\r\n") + except ssl.SSLEOFError as e: + print(f"[SSL ERROR] IRC connection closed: {e}") + self.irc = None # Mark socket dead except Exception as e: print(f"[-] Failed to send: {e}") # Send IRC commands to server def send_raw_command(self, command): + if not self.irc or not self.is_connected(): + print("[-] IRC connection is closed or unavailable.") + return if self.irc: try: - self.irc.send(f"{command}\r\n".encode("utf-8")) - print(f"[IRC CMD] {command}") + with self.lock: # Ensure thread-safe socket access + print(f"[QUEUE] Queuing message: {command.strip()}") + self.send_queue.put(f"{command}\r\n") + print(f"[IRC CMD] {command}") except Exception as e: print(f"[-] Failed to send IRC command: {e}") @@ -249,4 +289,40 @@ class IRCBot: user = parse_username(line) auth_db.remove_verified_user(user) + # Helper method for checking connection + def is_connected(self): + try: + if not self.irc: + return False + self.irc.getpeername() + return True + except Exception: + return False + + # IRC writer method to work with queue + def writer_loop(self): + print("[WRITER] Writer loop started") + while True: + try: + msg = self.send_queue.get() + print(f"[WRITER] Got message from queue: {msg.strip()}") + + if not self.irc or not self.is_connected(): + print("[-] Writer loop: IRC socket is down, message discarded.") + continue + + with self.lock: + print(f"[WRITER] Sending to IRC: {msg.strip()}") + self.irc.send(msg.encode("utf-8")) + + except (ConnectionResetError, BrokenPipeError, ssl.SSLError) as net_err: + print(f"[WRITER NET ERROR] Socket error: {net_err}") + self.irc = None + time.sleep(RECONNECT_DELAY) + continue + + except Exception as e: + print(f"[WRITER ERROR] Unexpected error: {e}") + time.sleep(1) + bot = IRCBot()