1
0
Fork 0

Update irc_bot.py

This commit is contained in:
Eric Lay 2025-05-18 16:50:46 -05:00
parent 0b1f91f5b7
commit 4982a7ecbf
1 changed files with 85 additions and 9 deletions

View File

@ -1,15 +1,16 @@
import queue
import redis
import requests
import socket import socket
import ssl
import threading import threading
import time import time
import re
import requests
import ssl
from auth_db import auth_db from auth_db import auth_db
from utils import strip_bbcode, parse_username from utils import strip_bbcode, parse_username
from config import ( from config import (
SERVER, PORT, BOTNICK, NICKSERV_USER, NICKSERV_PASS, API_ENDPOINT, API_TOKEN, VERIFY_ENDPOINT, SERVER, PORT, BOTNICK, NICKSERV_USER, NICKSERV_PASS, API_ENDPOINT, API_TOKEN, VERIFY_ENDPOINT,
LOBBY_CHANNEL, MAIN_CHANNEL, ADMIN_CHANNEL, STAFF_CHANNELS, 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: class IRCBot:
@ -18,6 +19,7 @@ class IRCBot:
self.nickname = BOTNICK self.nickname = BOTNICK
self.running = False self.running = False
self.lock = threading.Lock() self.lock = threading.Lock()
self.send_queue = queue.Queue()
# Connect to IRC server, handle nick conflicts, wait for welcome, and join channel # Connect to IRC server, handle nick conflicts, wait for welcome, and join channel
def connect(self): def connect(self):
@ -84,8 +86,13 @@ class IRCBot:
if self.running: if self.running:
return return
self.running = True self.running = True
print("[+] Starting IRC listener thread")
threading.Thread(target=self.listen_loop, daemon=True).start() 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): def listen_loop(self):
while True: while True:
@ -107,6 +114,21 @@ class IRCBot:
print(f"[-] IRC error: {e}") print(f"[-] IRC error: {e}")
self.connect() 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 # Handle verification messages from users
def handle_message(self, line): def handle_message(self, line):
user = parse_username(line) user = parse_username(line)
@ -197,25 +219,43 @@ class IRCBot:
# Send notice to user # Send notice to user
def send_notice(self, nick, msg): def send_notice(self, nick, msg):
if not self.irc or not self.is_connected():
print("[-] IRC connection is closed or unavailable.")
return
try: 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: except Exception as e:
print(f"[-] Notice failed: {e}") print(f"[-] Notice failed: {e}")
# Send IRC message to channel # Send IRC message to channel
def send_to_channel(self, msg): 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", " ") clean = strip_bbcode(msg).replace("\n", " ").replace("\r", " ")
try: 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: except Exception as e:
print(f"[-] Failed to send: {e}") print(f"[-] Failed to send: {e}")
# Send IRC commands to server # Send IRC commands to server
def send_raw_command(self, command): 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: if self.irc:
try: try:
self.irc.send(f"{command}\r\n".encode("utf-8")) with self.lock: # Ensure thread-safe socket access
print(f"[IRC CMD] {command}") print(f"[QUEUE] Queuing message: {command.strip()}")
self.send_queue.put(f"{command}\r\n")
print(f"[IRC CMD] {command}")
except Exception as e: except Exception as e:
print(f"[-] Failed to send IRC command: {e}") print(f"[-] Failed to send IRC command: {e}")
@ -249,4 +289,40 @@ class IRCBot:
user = parse_username(line) user = parse_username(line)
auth_db.remove_verified_user(user) 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() bot = IRCBot()