import socket import threading import time import re import requests from auth_db import auth_db from utils import strip_bbcode, parse_username from config import ( SERVER, PORT, BOTNICK, API_ENDPOINT, API_TOKEN, VERIFY_ENDPOINT, LOBBY_CHANNEL, MAIN_CHANNEL, ADMIN_CHANNEL, STAFF_CHANNELS, RECONNECT_DELAY, GROUP_NAMES, STAFF_GROUP_IDS, ADMIN_GROUP_IDS ) class IRCBot: def __init__(self): self.irc = None self.nickname = BOTNICK self.running = False self.lock = threading.Lock() # Connect to IRC server, handle nick conflicts, wait for welcome, and join channel def connect(self): attempt = 0 while True: try: self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.irc.connect((SERVER, PORT)) self.irc.send(f"NICK {self.nickname}\r\n".encode("utf-8")) self.irc.send(f"USER {self.nickname} 0 * :{self.nickname}\r\n".encode("utf-8")) print("[+] Sent NICK/USER, waiting...") while True: resp = self.irc.recv(2048) if not resp: raise ConnectionError("No data received.") for line in resp.decode(errors="ignore").split("\r\n"): if line.startswith("PING"): self.irc.send(f"PONG {line.split()[1]}\r\n".encode("utf-8")) elif " 001 " in line: print(f"[+] Connected as {self.nickname}") self.irc.send(f"JOIN {LOBBY_CHANNEL}\r\n".encode("utf-8")) return elif " 433 " in line: attempt += 1 self.nickname += "_" self.irc.close() time.sleep(2) break except Exception as e: print(f"[-] IRC connect error: {e}") time.sleep(RECONNECT_DELAY) def start(self): with self.lock: if self.running: return self.running = True threading.Thread(target=self.listen_loop, daemon=True).start() print("[+] IRC listener started") def listen_loop(self): while True: try: data = self.irc.recv(2048).decode(errors="ignore") if not data: raise ConnectionError("Disconnected.") for line in data.strip().split("\r\n"): if line.startswith("PING"): self.irc.send(f"PONG {line.split()[1]}\r\n".encode("utf-8")) elif "PRIVMSG" in line: self.handle_message(line) elif " PART " in line or " QUIT " in line: self.handle_join_leave(line) except Exception as e: print(f"[-] IRC error: {e}") self.connect() # Handle verification messages from users def handle_message(self, line): user = parse_username(line) if user.lower() == self.nickname.lower(): return # Extract target (channel or bot nick) and message try: prefix, trailing = line.split(" :", 1) _, _, target = prefix.strip().split(" ", 2) msg = trailing.strip() except ValueError: print(f"[-] Malformed PRIVMSG line: {line}") return is_private = target.lower() == self.nickname.lower() if msg.startswith("!verify"): if is_private: self.verify_user(user, msg) else: self.send_notice(user, "Please send !verify as a private message.") return # Relay public message if user is verified if not is_private: user_data = auth_db.get_verified_user(user) if user_data: site_user, irc_key, _ = user_data self.api_call(site_user, msg, irc_key) # Handle verification messages from users def verify_user(self, nick, msg): parts = msg.split(" ") if len(parts) != 3: self.send_notice(nick, "Usage: !verify ") return _, site_user, irc_key = parts try: resp = requests.post( VERIFY_ENDPOINT, headers={ "Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json" }, json={ "username": site_user, "irc_key": irc_key }, timeout=10 ) if resp.ok and resp.json().get("success"): group_id = resp.json().get("group_id", 4) group_name = GROUP_NAMES.get(group_id, "Unknown") auth_db.verify_user(nick, site_user, irc_key, group_id) self.send_notice(nick, f"Verified as {site_user} ({group_name})") # Join main lobby self.send_raw_command(f"SAJOIN {nick} {MAIN_CHANNEL}") # Join staff chats if in staff group if group_id in STAFF_GROUP_IDS: for channel in STAFF_CHANNELS: self.send_raw_command(f"SAJOIN {nick} {channel}") # Join admin room if in admin group if group_id in ADMIN_GROUP_IDS: self.send_raw_command(f"SAJOIN {nick} {ADMIN_CHANNEL}") else: self.send_notice(nick, f"Verification failed: {resp.json().get('error', 'Unknown')}") except Exception as e: print(f"[-] Error verifying IRC key: {e}") self.send_notice(nick, "Verification failed (server error)") # Send notice to user def send_notice(self, nick, msg): try: self.irc.send(f"NOTICE {nick} :{msg}\r\n".encode("utf-8")) except Exception as e: print(f"[-] Notice failed: {e}") # Send IRC message to channel def send_to_channel(self, msg): clean = strip_bbcode(msg).replace("\n", " ").replace("\r", " ") try: self.irc.send(f"PRIVMSG {LOBBY_CHANNEL} :{clean}\r\n".encode("utf-8")) except Exception as e: print(f"[-] Failed to send: {e}") # Send IRC commands to server def send_raw_command(self, command): if self.irc: try: self.irc.send(f"{command}\r\n".encode("utf-8")) print(f"[IRC CMD] {command}") except Exception as e: print(f"[-] Failed to send IRC command: {e}") # API call ChatBridgeController def api_call(self, user, msg, token): try: requests.post(API_ENDPOINT, headers={ "Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json" }, json={"username": user, "text": msg, "token": token}) except Exception as e: print(f"[-] API call failed: {e}") def handle_join_leave(self, line): user = parse_username(line) auth_db.remove_verified_user(user) bot = IRCBot()