From 4d99fa7d4e6d4b1f7a378ec410b716b446d01663 Mon Sep 17 00:00:00 2001 From: kalzu rekku Date: Thu, 8 Feb 2024 20:01:34 +0200 Subject: [PATCH] Inital commit after 3 days of coding... --- config.ini | 7 +++ ircthing3.py | 138 ++++++++++++++++++++++++++++++++++++++++ ircthing_core.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 config.ini create mode 100644 ircthing3.py create mode 100644 ircthing_core.py diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..aab6623 --- /dev/null +++ b/config.ini @@ -0,0 +1,7 @@ +[ircnet] +server = irc.ircnet.com +port = 6667 +nickname = KalzuTest +;password = server_password +;channels = [list, of, channels] + diff --git a/ircthing3.py b/ircthing3.py new file mode 100644 index 0000000..c779596 --- /dev/null +++ b/ircthing3.py @@ -0,0 +1,138 @@ +import threading +import os +import sys +import time +import socket +import configparser +from ircthing_core import irc_router + +Threads = {} +Stop_Toggle = threading.Event() + +def read_config(config_path): + config = configparser.ConfigParser() + config.read(config_path) + + network_configs = [] + + try: + # Collect information from each topic / network + for topic in config.sections(): + network = config[topic] + server = network.get("server") + port = network.getint("port") + channels = network.get("channels", fallback=None) + nickname = network.get("nickname") + password = network.get("password", fallback=None) + + network_config = { + "net_name": network.name, + "server": server, + "port": port, + "channels": channels, + "nickname": nickname, + "password": password, + } + network_configs.append(network_config) + return network_configs + except Exception as e: + print(f"Failure while reading configuration file. {e}") + exit(1) + +def clean_exit(fifo_files, socket): + socket.send(bytes(f"QUIT :Bye\r\n", "UTF-8")) + Stop_Toggle.set() + for file in fifo_files: + try: + os.unlink(file) + except FileNotFoundError: + # We are okay if some fifo files has been removed before this. + pass + +def make_files(path, net_name): + + os.makedirs(path, exist_ok=True) + server_dir = os.path.join(path, net_name) + os.makedirs(server_dir, exist_ok=True) + try: + os.mkfifo(f"{server_dir}/in") + except FileExistsError: + pass + try: + os.mkfifo(f"{server_dir}/out") + except FileExistsError: + pass + fifo_files = [] + fifo_files.append(f"{server_dir}/in") + fifo_files.append(f"{server_dir}/out") + return fifo_files + +def connect_to_irc_server( + base_path, + net_name, + server, + port, + nickname, + password=None, +): + + print(f"Going to connect to: {server}") + # Create a socket connection to the IRC server + irc_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + irc_socket.connect((server, port)) + + # Send the server password if provided + if password: + irc_socket.send(bytes(f"PASS {password}\r\n", "UTF-8")) + + print(f"Going to use username: {nickname}") + # Send user and nickname information to the server + irc_socket.send(bytes(f"USER {nickname} 0 * :{nickname}\r\n", "UTF-8")) + irc_socket.send(bytes(f"NICK {nickname}\r\n", "UTF-8")) + + # Create directories for the server and channel + fifo_files = make_files(base_path, net_name) + + return irc_socket, fifo_files + +def main(): + my_name = sys.argv[0] + my_name_pyless, _ = os.path.splitext(my_name) + base_path = f'/tmp/{my_name_pyless}' + + config_path = '../ircthing3.ini' + + # Read configurations for all topics + network_configs = read_config(config_path) + + ## Get irc socket for each network in configuration + ## Start thread for each socket + + for network in network_configs: + net_name = network["net_name"] + print(f"{time.time()} | Found configs for {net_name} network.") + server = network["server"] + port = network["port"] + nickname = network["nickname"] + password = network["password"] + irc_socket, fifo_files = connect_to_irc_server(base_path, net_name, server, port, nickname, password) + + router_instance = irc_router(fifo_files, irc_socket, server, nickname) + Threads[net_name] = threading.Thread(target=router_instance.start) + Threads[net_name].daemon = True + Threads[net_name].start() + + for thread in Threads.values(): + print(thread) + thread.join() + + +if __name__ == "__main__": + print(f"{time.time()} | Lets start!") + try: + main() + except Exception as e: + print(f"Got error {e}") + finally: + print(f"{time.time()} | Bye!") + exit(0) diff --git a/ircthing_core.py b/ircthing_core.py new file mode 100644 index 0000000..e78e934 --- /dev/null +++ b/ircthing_core.py @@ -0,0 +1,161 @@ +import time +import re +import asyncio +import os + +class irc_router: + def __init__(self, fifo_files, irc_socket, server, nickname): + self.fifo_files = fifo_files + self.irc_socket = irc_socket + self.server = server + self.nickname = nickname + self.channels = [] + self._loop = asyncio.new_event_loop() + + def start(self): + try: + asyncio.set_event_loop(self._loop) + tasks = [self.user_listener(), self.irc_message_loop()] + self._loop.run_until_complete(asyncio.gather(*tasks)) + finally: + for file in self.fifo_files: + try: + os.close(file) + except: + # Errors are okay at this point. + pass + self._loop.close() + + ## Main loop for reading irc inflow and routing to correct _out fifos. + ## Also handle ping/pong here + async def irc_message_loop(self): + output_files=[] + loop = asyncio.get_running_loop() + while True: + + for output in self.fifo_files: + if output.endswith('out'): + if output not in output_files: + print(f"{time.time()} | {self.server} has output file: {output}") + output_files.append(output) + + try: + irc_input = await loop.sock_recv(self.irc_socket, 2048) + irc_input = irc_input.decode("utf-8") + except Exception as e: + print(f"Error reading from socket: {e}") + continue + + if irc_input: + ## + ## The main logic hapens here. + ## support for channel joins + ## and spliting the input to channel specifig channel/[in, out] locations + ## + if "PING" in irc_input: + self.irc_send(f"PONG :{self.server}") + self.write_to_out(output_files[0], f"{time.time()} | ping/pong") + continue + + if f":{self.server} 353" in irc_input: + ## We have joined a channel! + match = re.search(r'353 \S+ = (#\S+)', irc_input) + channel_join = match.group(1) + await self.make_files(channel_join) + continue + + if "PRIVMSG" in irc_input: + if self.if_input_belongs_to_channel(irc_input): + continue + + self.write_to_out(output_files[0], irc_input) + + await asyncio.sleep(0.1) + + def if_input_belongs_to_channel(self, input): + # We know that input includes "PRIVMSG" + # But to where it shoudl go? + message = re.search(r':.*:(.*)', input).group(1) + input_user = re.search(r'^:(\S+)', input).group(1) + input_channel = re.search(r'PRIVMSG (\S+) :', input) + done=False + for file in self.fifo_files: + if input_channel.group(1) in file: + if file.endswith("out"): + self.write_to_out(file, f"{input_user}: {message}") + done=True + return done + + async def make_files(self, channel): + network_path = os.path.dirname(self.fifo_files[1]) + channel_dir = os.path.join(network_path, channel) + os.makedirs(channel_dir, exist_ok=True) + try: + os.mkfifo(f"{channel_dir}/in") + except FileExistsError: + pass + try: + os.mkfifo(f"{channel_dir}/out") + except FileExistsError: + pass + fifo_files = [] + fifo_files.append(f"{channel_dir}/in") + fifo_files.append(f"{channel_dir}/out") + self.fifo_files.extend(fifo_files) + await asyncio.sleep(1) + + def write_to_out(self, file, input): + if file == None: + print(f"{time.time()} | Most likely this should not be here: {input}") + with open(file, "a") as output_sink: + output_sink.write(input + "\r\n") + output_sink.flush() + output_sink.close() + + def irc_send(self, message): + # Send message to the IRC server + self.irc_socket.send(bytes(f"{message}\r\n", "utf-8")) + print(f"{time.time()} | Send: {message}") + + async def async_readline(self, file, queue): + print(f"Trying to read from file {file}") + try: + while True: + with open(file, mode='r') as in_file: + line = in_file.readline().strip() + if not line: + print("There was no line!") + continue + await queue.put(line) + in_file.close() + await asyncio.sleep(0.5) + except Exception as e: + print(f"ERROR IN async_readline func: {e}") + + async def process_user_input(self, queue): + print("Processing of user input may start!") + while True: + line = await queue.get() + print(f"{time.time()} | trying to send: {line} to {self.server}") + + self.irc_send(line) + await asyncio.sleep(0.2) + + async def user_listener(self): + print("User Listener is HERE!") + in_files = [] + queue = asyncio.Queue() + try: + for file in self.fifo_files: + if file.endswith("in"): + print(f"We have input fifo {file}") + in_files.append(file) + + tasks = [self.process_user_input(queue)] + tasks.extend([self.async_readline(file, queue) for file in in_files]) + + # Wait for all tasks to complete + await asyncio.gather(*tasks) + finally: + self.irc_send('QUIT') +