import os import re import requests import smtplib import socket import ssl import random import time from datetime import datetime from email.mime.text import MIMEText DEBUG_LOG = os.environ["DEBUG_LOG"].lower() == "true" IRC_SERVER = os.environ["IRC_SERVER"] IRC_PORT = int(os.environ["IRC_PORT"]) USE_TLS = os.environ["IRC_USE_TLS"].lower() == "true" IRC_NICK = os.environ["IRC_NICK"] IRC_USER = IRC_NICK IRC_REALNAME = IRC_NICK IRC_CHANNEL = os.environ["IRC_CHANNEL"] TOPIC_REGEX = os.environ["TOPIC_REGEX"] SMTP_SERVER = os.environ["SMTP_SERVER"] SMTP_PORT = int(os.environ["SMTP_PORT"]) SMTP_USER = os.environ["SMTP_USER"] SMTP_PASS = os.environ["SMTP_PASS"] EMAIL_TO = os.environ["EMAIL_TO"] MIN_WAIT_TIME = int(os.environ["MIN_WAIT_TIME"]) MAX_WAIT_TIME = int(os.environ["MAX_WAIT_TIME"]) MIN_SLEEP_TIME = int(os.environ["MIN_SLEEP_TIME"]) MAX_SLEEP_TIME = int(os.environ["MAX_SLEEP_TIME"]) topic_pattern = re.compile(TOPIC_REGEX) def log(level, msg): if level == "." and not DEBUG_LOG: return ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") print(f"[{ts}] [{level}] {msg}", flush=True) def sendmail(subject, message): try: msg = MIMEText(message) msg["Subject"] = subject msg["From"] = SMTP_USER msg["To"] = EMAIL_TO with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: server.starttls() server.login(SMTP_USER, SMTP_PASS) server.send_message(msg) log("+", f"Email sent to {EMAIL_TO}") except Exception as e: log("!", f"Failed to send error email: {e}") def connect_to_channel_and_check_topic(): log("*", f"Connecting to {IRC_SERVER}:{IRC_PORT} {"with TLS" if USE_TLS else "plain"}") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) if USE_TLS: context = ssl.create_default_context() sock = context.wrap_socket(sock, server_hostname=IRC_SERVER) sock.connect((IRC_SERVER, IRC_PORT)) # Send NICK and USER nick = f"NICK {IRC_NICK}\r\n".encode() log(".", nick) sock.sendall(nick) irc_user = f"USER {IRC_USER} 0 * :{IRC_REALNAME}\r\n".encode() log(".", irc_user) sock.sendall(irc_user) topic = None joined = False while True: data = sock.recv(4096) if not data: break lines = data.decode(errors="ignore").split("\r\n") for line in lines: if not line: continue log(".", f"< {line}") # Respond to PING if line.startswith("PING"): resp = line.replace("PING", "PONG") pong = f"{resp}\r\n".encode() sock.sendall(pong) log(".", pong) # Check for end of MOTD or welcome to join channel if " 001 " in line and not joined: # Join channel join = f"JOIN {IRC_CHANNEL}\r\n".encode() sock.sendall(join) log(".", join) joined = True # Capture topic sent by server upon joining # IRC servers send: :server 332 nick #channel :topic here if f" 332 {IRC_NICK} " in line: parts = line.split(":", 2) if len(parts) == 3: topic = parts[2] log("*", f"Channel topic on join: {topic}") if topic_pattern.search(topic): log("+", f"Found topic, sending mail to {EMAIL_TO}") sendmail("IRC TOPIC BOT: Found topic", topic) # If we have the topic, we can disconnect after a random delay if topic is not None: wait_time = random.randint(MIN_WAIT_TIME, MAX_WAIT_TIME) log("*", f"Waiting for {wait_time} seconds before disconnecting") time.sleep(wait_time) bye = b"QUIT :Bye!\r\n" sock.sendall(bye) log(".", bye) sock.close() log("*", "Disconnected") return def main(): try: res = requests.get("https://api.ipify.org", timeout=10) res.raise_for_status() ip = res.text.strip() log("*", f"Public IP is {ip}") except Exception as e: log("!", f"Could not get public IP, {e}") while True: connect_to_channel_and_check_topic() sleep_time = random.randint(MIN_SLEEP_TIME, MAX_SLEEP_TIME) log("*", f"Sleeping for {sleep_time} seconds before reconnecting") time.sleep(sleep_time) if __name__ == "__main__": main()