1
0
Fork 0
ircBridgeV3/irc_bot.py

198 lines
7.1 KiB
Python

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 <username> <irc_key>")
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()