IRCat/server.py

569 lines
36 KiB
Python
Raw Normal View History

2024-12-08 16:54:04 -08:00
#!/usr/bin/python3
2024-12-08 20:06:57 -08:00
__version__ = "0.0.1-pre-alpha"
2024-12-24 16:50:04 -08:00
print(f"Codename IRCat v{__version__}")
2024-12-08 16:54:04 -08:00
print("Welcome! /ᐠ ˵> ⩊ <˵マ")
2024-12-12 11:39:41 -08:00
import socket, time, threading, traceback, sys, subprocess, yaml, sqlite3, os, bcrypt
2024-12-08 16:54:04 -08:00
from requests import get
2024-12-08 18:55:43 -08:00
if not len(sys.argv) == 2:
print("IRCat requires the following arguments: config.yml")
2024-12-08 19:04:37 -08:00
sys.exit(1)
server = "127.0.0.1"
displayname = "foo"
2024-12-09 15:33:56 -08:00
identifier = "somewhere in the universe"
2024-12-11 12:54:12 -08:00
admin_nick = "admin"
data_path = ""
2024-12-15 15:39:48 -08:00
motd = """
____ _ ___ ____ ____ _
/ ___|___ __| | ___ _ __ __ _ _ __ ___ ___ |_ _| _ \ / ___|__ _| |_
| | / _ \ / _` |/ _ \ '_ \ / _` | '_ ` _ \ / _ \ | || |_) | | / _` | __|
| |__| (_) | (_| | __/ | | | (_| | | | | | | __/ | || _ <| |__| (_| | |_
2024-12-16 09:15:45 -08:00
\____\___/ \__,_|\___|_| |_|\__,_|_| |_| |_|\___| |___|_| \_\\\\____\__,_|\__|
2024-12-15 15:39:48 -08:00
https://ircat.xyz
2024-12-22 16:54:52 -08:00
This server doesn't have a MOTD in its configuration, or is invalid."""
2024-12-15 15:39:48 -08:00
motd_file = None
2024-12-15 19:30:22 -08:00
ping_timeout = 255
2024-12-08 19:04:37 -08:00
with open(sys.argv[1], 'r') as file:
data = yaml.safe_load(file)
try: server = data["host"]
except: print("using fallback server address")
try: displayname = data["name"]
except: print("using fallback display name")
2024-12-09 15:39:05 -08:00
try: identifier = data["identifier"]
2024-12-09 15:33:56 -08:00
except: print("using fallback identifier")
2024-12-11 12:54:12 -08:00
try: admin_nick = data["admin-nick"]
except: print("using fallback admin nick")
try: data_path = data["data-path"]
except:
print("IRCat requires \"data-path\" in config.yml")
sys.exit(1)
2024-12-15 15:39:48 -08:00
try: motd = data["motd"]
except: print("using fallback MOTD")
try: motd_file = data["motd-file"]
except: print("Not reading MOTD from a file.")
2024-12-15 19:30:22 -08:00
try: ping_timeout = int(data["ping-timeout"])
except: print("Using 255 as a ping timeout.")
2024-12-08 19:04:37 -08:00
file.close()
2024-12-08 20:03:55 -08:00
print("Successfully loaded config!")
2024-12-11 14:50:16 -08:00
class IRCat_DATA_BROKER:
def __init__(self):
if not os.path.isfile(data_path):
print("Creating database file...")
open(data_path, "w").write("")
self.conn = sqlite3.connect(data_path)
2024-12-12 11:48:41 -08:00
db = self.conn.cursor()
2024-12-12 11:52:20 -08:00
print("Creating NickServ table...")
2024-12-13 13:03:06 -08:00
db.execute("""CREATE table IF NOT EXISTS nickserv (user varchar(255), modes varchar(255), hash varchar(255), cloak varchar(255))""")
2024-12-12 11:52:20 -08:00
print("Creating Groups table...")
db.execute("""CREATE table IF NOT EXISTS groups (name varchar(255), owner varchar(255))""")
print("Creating ChanServ table...")
db.execute("""CREATE table IF NOT EXISTS chanserv (name varchar(255), modes varchar(255), params varchar(255), owner varchar(255), usermodes varchar(255), optimodes varchar(255))""")
2024-12-12 11:48:41 -08:00
def nickserv_identify(self, nick, password:str):
db = self.conn.cursor()
password = password.encode("UTF-8")
db.execute("SELECT * FROM nickserv WHERE user=?;", [nick])
2024-12-12 11:49:41 -08:00
if db.fetchall() == []:
2024-12-12 11:48:41 -08:00
return ["Nickname doesn't exist."]
2024-12-12 11:39:41 -08:00
2024-12-12 09:56:55 -08:00
config = IRCat_DATA_BROKER()
2024-12-08 16:54:04 -08:00
ip = get('https://api.ipify.org').content.decode('utf8')
2024-12-08 17:36:59 -08:00
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2024-12-09 12:52:13 -08:00
server_address = ('', 6667)
2024-12-08 16:54:04 -08:00
tcp_socket.bind(server_address)
tcp_socket.listen(1)
2024-12-11 14:59:32 -08:00
opened=True
2024-12-09 14:35:01 -08:00
reserved = ["nickserv", "chanserv", "gitserv"] # Reserved nicknames
2024-12-09 12:27:44 -08:00
nickname_list = {} # Stores nicknames and the respective sockets
2024-12-13 13:03:06 -08:00
lower_nicks = {"gitserv": "GitServ", "nickserv": "NickServ"} # Nicknames in lowercase
2024-12-09 12:27:44 -08:00
channels_list = {} # Store channels and their user lists
2024-12-22 16:54:52 -08:00
property_list = {"GitServ": {"host": "IRCatCore", "username": "IRCat", "realname": "Codename IRCat Integrated services - Updates bot"},"NickServ": {"host": "IRCatCore", "username": "IRCat", "realname": "Codename IRCat Integrated services - Login bot", "away": False}} # Stores properties for active users and channels
2024-12-08 16:54:04 -08:00
print("Now listening on port 6667")
2024-12-09 16:45:21 -08:00
def pinger(nick, connection):
2024-12-10 21:35:50 -08:00
global property_list
2024-12-09 16:45:21 -08:00
while nick in property_list:
2024-12-15 19:29:27 -08:00
if (time.time() - property_list[nick]["last_ping"]) > 30 and not property_list[nick]["ping_pending"]:
2024-12-12 13:35:02 -08:00
if nick in property_list:
print("Sent ping message to " + nick)
property_list[nick]["ping_pending"] = True
time.sleep(0.5)
connection.sendall(bytes(f"PING {server}\r\n","UTF-8"))
2024-12-15 19:29:27 -08:00
elif property_list[nick]["ping_pending"] and ((time.time() - property_list[nick]["last_ping"]) > ping_timeout):
2024-12-12 13:35:02 -08:00
if nick in property_list:
2024-12-15 19:29:27 -08:00
property_list[nick]["cause"] = f"Ping timeout: {ping_timeout} seconds"
2024-12-12 13:45:48 -08:00
print("SHUTTING DOWN FOR " + nick)
2024-12-12 13:35:02 -08:00
connection.shutdown(socket.SHUT_WR)
connection.close()
break
2024-12-08 16:54:04 -08:00
def session(connection, client):
2024-12-15 19:08:18 -08:00
global property_list
2024-12-09 09:30:20 -08:00
pending = "*" # The nickname of the client
2024-12-08 20:02:59 -08:00
already_set = False # If the client gave the server a NICK packet
ready = False # If the client gave the server a USER packet
finished = False # If the server gave the client its information, indicating it's ready.
2024-12-09 14:08:31 -08:00
username = "oreo" # Username/ident specified by client
hostname = "" # Hostname, can be IP or domain
realname = "realname" # Realname specified by client
safe_quit = False # If the client safely exited, or if the server should manually drop the connection
cause = "Unknown" # The cause of the unexpected exit
2024-12-08 16:54:04 -08:00
try:
print("Connected to client IP: {}".format(client))
connection.sendall(bytes(f":{server} NOTICE * :*** Looking for your hostname...\r\n","UTF-8"))
2024-12-09 12:41:46 -08:00
try:
hostname = socket.gethostbyaddr(client[0])[0]
connection.sendall(bytes(f":{server} NOTICE * :*** Got it! {hostname}\r\n","UTF-8"))
except:
hostname = client[0]
connection.sendall(bytes(f":{server} NOTICE * :*** Oof! Can't find your hostname, using IP...\r\n","UTF-8"))
2024-12-08 16:54:04 -08:00
while True:
try:
data = connection.recv(2048)
2024-12-09 21:58:05 -08:00
except Exception as ex:
cause = "Read error: " + str(ex)
break
2024-12-08 16:54:04 -08:00
print("Received data: {}".format(data))
try:
textt = data.decode()
2024-12-11 22:32:38 -08:00
for text in textt.replace("\r", "").split("\n"):
2024-12-09 11:16:30 -08:00
command = text.split(" ")[0].upper()
try:
args = text.split(" ")[1:]
except:
pass
2024-12-12 13:12:10 -08:00
if command == "NICK" and not finished:
2024-12-08 16:54:04 -08:00
pending = text.split(" ")[1]
2024-12-09 15:33:56 -08:00
if pending[0] == ":": pending[1:]
2024-12-09 21:43:11 -08:00
if "!" in pending or ":" in pending or "#" in pending or "*" in pending:
connection.sendall(bytes(f":{server} 432 * {pending} :Erroneus nickname\r\n","UTF-8"))
pending = "*"
elif pending.lower() in lower_nicks or pending in reserved:
2024-12-08 16:54:04 -08:00
connection.sendall(bytes(f":{server} 433 * {pending} :Nickname is already in use.\r\n","UTF-8"))
2024-12-09 09:30:20 -08:00
pending = "*"
2024-12-08 16:54:04 -08:00
else:
2024-12-12 08:45:11 -08:00
already_set = True
print(f"User {pending} set nick")
2024-12-09 11:07:26 -08:00
elif command == "USER":
2024-12-08 16:54:04 -08:00
if not ready:
username = text.split(" ")[1]
2024-12-09 13:44:15 -08:00
realname = " ".join(text.split(" ")[4:])[1:]
2024-12-08 20:06:57 -08:00
ready = True
2024-12-09 13:33:03 -08:00
elif command == "CAP":
2024-12-09 13:33:26 -08:00
if args[0] == "LS":
connection.sendall(bytes(f":{server} CAP * LS :ircat.xyz/foo\r\n", "UTF-8"))
2024-12-08 20:02:59 -08:00
elif (ready and already_set) and not finished:
2024-12-15 19:29:27 -08:00
cleanup_manual()
2024-12-11 22:01:28 -08:00
print(f"User {pending} successfully logged in.")
2024-12-09 14:35:01 -08:00
nickname_list[pending] = connection
2024-12-22 16:54:52 -08:00
property_list[pending] = {"host": hostname, "username": username, "realname": realname, "modes": "iw", "last_ping": time.time(), "ping_pending": False, "away": False}
2024-12-09 15:57:24 -08:00
lower_nicks[pending.lower()] = pending
2024-12-09 16:45:36 -08:00
threading.Thread(target=pinger, args=[pending, connection]).start()
2024-12-08 20:02:59 -08:00
connection.sendall(bytes(f":{server} 001 {pending} :Welcome to the {displayname} Internet Relay Chat Network {pending}\r\n", "UTF-8"))
connection.sendall(bytes(f":{server} 002 {pending} :Your host is {server}[{ip}/6667], running version IRCat-v{__version__}\r\n", "UTF-8"))
connection.sendall(bytes(f":{server} 004 {pending} {server} IRCat-{__version__} iow ovmsitnlbkq\r\n", "UTF-8"))
2024-12-15 05:16:27 -08:00
connection.sendall(bytes(f":{server} 005 {pending} NETWORK={displayname} :are supported by this server\r\n", "UTF-8"))
2024-12-15 15:39:48 -08:00
# connection.sendall(bytes(f":{server} 251 {pending} :There are {allusers} users and {allinvis} invisible in {servers} servers\r\n", "UTF-8")) Not supported as there isn't multi-server capability (yet)
ops = 0 # Placeholder, will replace with caclulating how much people have +o
connection.sendall(bytes(f":{server} 252 {pending} {ops} :IRC Operators online\r\n", "UTF-8"))
connection.sendall(bytes(f":{server} 253 {pending} 0 :unknown connection(s)\r\n", "UTF-8")) # Replace 0 with a variable of not setup clients.
chans = len(channels_list)
connection.sendall(bytes(f":{server} 254 {pending} {chans} :channels formed\r\n", "UTF-8"))
cleints = len(nickname_list)
servers = 1
connection.sendall(bytes(f":{server} 255 {pending} :I have {cleints} clients and {servers} servers\r\n", "UTF-8"))
# Start the MOTD
if motd_file != None:
motd = open(motd_file).read()
connection.sendall(bytes(f":{server} 375 {pending} :- {server} Message of the Day -\r\n", "UTF-8"))
for i in motd.rstrip().split("\n"):
connection.sendall(bytes(f":{server} 376 {pending} :- {i}\r\n", "UTF-8"))
connection.sendall(bytes(f":{server} 372 {pending} :End of /MOTD command\r\n", "UTF-8"))
# End the MOTD
2024-12-08 20:02:59 -08:00
connection.sendall(bytes(f":{pending} MODE {pending} +iw\r\n","UTF-8"))
finished = True
2024-12-09 11:07:26 -08:00
elif command == "PING":
2024-12-09 21:56:12 -08:00
e = text.split(" ")[1]
2024-12-10 10:25:15 -08:00
print("Replying with \"" + str([f":{server} PONG {server} :{e}\r\n"]) + "\"")
connection.sendall(bytes(f":{server} PONG {server} :{e}\r\n","UTF-8"))
2024-12-15 15:39:48 -08:00
elif command == "MOTD":
if motd_file != None:
motd = open(motd_file).read()
connection.sendall(bytes(f":{server} 375 {pending} :- {server} Message of the Day -\r\n", "UTF-8"))
for i in motd.rstrip().split("\n"):
connection.sendall(bytes(f":{server} 376 {pending} :- {i}\r\n", "UTF-8"))
connection.sendall(bytes(f":{server} 372 {pending} :End of /MOTD command\r\n", "UTF-8"))
2024-12-12 13:12:10 -08:00
elif finished:
2024-12-09 11:07:26 -08:00
if command == "JOIN":
2024-12-09 12:04:47 -08:00
channels = text.split(" ")[1]
for channelt in channels.split(","):
channel = channelt.strip()
success = True
if channel in channels_list:
if pending in channels_list[channel]:
success=False
print(f"{pending} is already in {channel} , ignoring JOIN request.")
if success:
try:
2024-12-09 12:04:47 -08:00
if channel in channels_list:
channels_list[channel].append(pending)
else:
channels_list[channel] = [pending]
except:
2024-12-09 12:04:47 -08:00
connection.sendall(bytes(f":{server} NOTICE * :*** Could not join {channel}\r\n","UTF-8"))
print(channels_list)
for i in channels_list[channel]:
try:
2024-12-09 13:26:23 -08:00
nickname_list[i].sendall(bytes(f":{pending}!~{username}@{hostname} JOIN {channel}\r\n","UTF-8"))
2024-12-09 12:04:47 -08:00
except:
pass
2024-12-09 13:26:23 -08:00
# Code re-used in the NAMES command
2024-12-09 12:04:47 -08:00
if channel in channels_list:
if pending in channels_list[channel]:
users = " ".join(channels_list[channel])
connection.sendall(bytes(f":{server} 353 {pending} = {channel} :{users}\r\n","UTF-8"))
connection.sendall(bytes(f":{server} 366 {pending} {channel} :End of /NAMES list.\r\n","UTF-8"))
2024-12-09 12:41:46 -08:00
print("Successfully pre-loaded /NAMES list")
2024-12-09 16:20:40 -08:00
elif command == "PONG":
e = text.split(" ")[1]
if e == server:
2024-12-09 16:46:15 -08:00
print(pending + " replied to PING.")
2024-12-09 16:45:21 -08:00
property_list[pending]["last_ping"] = time.time()
property_list[pending]["ping_pending"] = False
2024-12-12 13:22:59 -08:00
elif command == "NICK":
2024-12-12 13:12:10 -08:00
if len(args) == 0:
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-13 13:37:52 -08:00
elif text.split(" ")[1] == pending:
pass
2024-12-12 13:12:10 -08:00
else:
pending2 = text.split(" ")[1]
if pending2[0] == ":": pending2[1:]
if "!" in pending2 or ":" in pending2 or "#" in pending2 or "*" in pending2:
connection.sendall(bytes(f":{server} 432 {pending} {pending2} :Erroneus nickname\r\n","UTF-8"))
2024-12-12 13:19:48 -08:00
elif pending2.lower() in lower_nicks or pending2 in reserved:
2024-12-12 13:12:10 -08:00
connection.sendall(bytes(f":{server} 433 {pending} {pending2} :Nickname is already in use.\r\n","UTF-8"))
else:
2024-12-12 13:52:18 -08:00
print("Sending nickname change...")
2024-12-12 13:56:22 -08:00
connection.sendall(bytes(f":{pending}!~{username}@{hostname} NICK {pending2}\r\n","UTF-8"))
2024-12-12 13:12:10 -08:00
# Broadcast the nickname change
done = []
for i, users in channels_list.items():
2024-12-12 13:15:01 -08:00
if pending in users:
for j in users:
2024-12-12 13:28:45 -08:00
if j != pending and j != pending2 and not j in done:
2024-12-16 09:28:49 -08:00
print("Broadcasting on " + j)
2024-12-12 13:15:01 -08:00
nickname_list[j].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
done.append(j)
# Replace the nickname
try:
2024-12-12 13:45:48 -08:00
print("Changing on " + i)
2024-12-12 13:15:01 -08:00
channels_list[i].remove(pending)
channels_list[i].append(pending2)
except:
print(traceback.format_exc())
2024-12-16 09:28:49 -08:00
print("Moving config...")
property_list[pending2] = property_list.pop(pending)
nickname_list[pending2] = nickname_list.pop(pending)
del lower_nicks[pending.lower()]
lower_nicks[pending2.lower()] = pending2
print("starting pinger...")
pending = pending2
property_list[pending2]["ping_pending"] = False
property_list[pending2]["last_ping"] = time.time()
threading.Thread(target=pinger, args=[pending, connection]).start()
print(f"User {pending} set nick")
print("Broadcasting nickname change...")
2024-12-09 11:07:26 -08:00
elif command == "PART":
2024-12-09 18:27:40 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
else:
channel = text.split(" ")[1]
for i in channels_list[channel]:
try:
nickname_list[i].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
except:
pass
2024-12-08 20:02:59 -08:00
try:
2024-12-09 18:27:40 -08:00
channels_list[channel].remove(pending)
2024-12-08 20:02:59 -08:00
except:
2024-12-09 18:27:40 -08:00
print(traceback.format_exc())
2024-12-22 16:54:52 -08:00
elif command == "AWAY":
if len(args) == 0:
property_list[pending]["away"] = False
connection.sendall(bytes(f":{server} 305 {pending} :You are no longer marked as being away\r\n","UTF-8"))
else:
reasons = " ".join(args)
if reasons[0] == ":": reasons = reasons[1:]
property_list[pending]["away"] = True
property_list[pending]["reason"] = reasons
connection.sendall(bytes(f":{server} 306 {pending} :You have been marked as being away\r\n","UTF-8"))
2024-12-09 11:07:26 -08:00
elif command == "WHO":
2024-12-09 18:27:40 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
else:
channel = text.split(" ")[1]
if channel in channels_list:
for i in channels_list[channel]:
who_host = property_list[i]["host"]
who_user = property_list[i]["username"]
who_realname = property_list[i]["realname"]
2024-12-22 16:54:52 -08:00
who_away = "G" if property_list[i]["away"] else "H"
connection.sendall(bytes(f":{server} 352 {pending} {channel} ~{who_user} {who_host} {server} {i} {who_away} :0 {who_realname}\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
elif channel in nickname_list:
who_host = property_list[channel]["host"]
who_user = property_list[channel]["username"]
who_realname = property_list[channel]["realname"]
2024-12-22 16:54:52 -08:00
who_away = "G" if property_list[channel]["away"] else "H"
connection.sendall(bytes(f":{server} 352 {pending} * ~{who_user} {who_host} {server} {channel} {who_away} :0 {who_realname}\r\n","UTF-8"))
2024-12-09 12:27:44 -08:00
2024-12-12 16:38:26 -08:00
connection.sendall(bytes(f":{server} 315 {pending} {channel} :End of /WHO list.\r\n","UTF-8"))
2024-12-09 14:35:01 -08:00
elif command == "WHOIS":
2024-12-09 18:27:40 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 14:35:01 -08:00
else:
2024-12-09 18:27:40 -08:00
target = text.split(" ")[1]
if target.lower() in lower_nicks:
target = lower_nicks[target.lower()]
2024-12-09 18:27:40 -08:00
if target in property_list:
who_user = property_list[target]["username"]
who_realname = property_list[target]["realname"]
who_host = property_list[target]["host"]
2024-12-09 19:31:48 -08:00
try:
who_flags = property_list[target]["modes"]
except:
who_flags = None
connection.sendall(bytes(f":{server} 311 {pending} {target} ~{who_user} {who_host} * :{who_realname}\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
connection.sendall(bytes(f":{server} 312 {pending} {target} {server} :{identifier}\r\n","UTF-8"))
2024-12-22 16:54:52 -08:00
if "o" in who_flags: connection.sendall(bytes(f":{server} 313 {pending} {target} :is an IRC operator\r\n","UTF-8"))
who_away = property_list[target]["away"]
if who_away:
who_reason = who_away = property_list[target]["reason"]
connection.sendall(bytes(f":{server} 301 {pending} {target} :{who_reason}\r\n","UTF-8"))
#connection.sendall(bytes(f":{server} 317 {pending} {target} {time} :seconds idle\r\n","UTF-8")) # I haven't implemented idle time yet.
2024-12-09 19:31:48 -08:00
if who_flags != None and who_flags != "iw":
connection.sendall(bytes(f":{server} 379 {pending} {target} :Is using modes +{who_flags}\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
connection.sendall(bytes(f":{server} 318 {pending} {target} :End of /WHOIS list\r\n","UTF-8"))
else:
connection.sendall(bytes(f":{server} 401 {pending} {target} :No such nick/channel\r\n","UTF-8"))
2024-12-09 13:26:23 -08:00
elif command == "NAMES":
2024-12-09 18:27:40 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
else:
channel = text.split(" ")[1]
if channel in channels_list:
if pending in channels_list[channel]:
users = " ".join(channels_list[channel])
connection.sendall(bytes(f":{server} 353 {pending} = {channel} :{users}\r\n","UTF-8"))
connection.sendall(bytes(f":{server} 366 {pending} {channel} :End of /NAMES list.\r\n","UTF-8"))
2024-12-09 18:15:10 -08:00
elif command == "NOTICE":
2024-12-09 18:27:40 -08:00
if len(args) >= 2:
target = text.split(" ")[1]
if target.lower() in lower_nicks:
target = lower_nicks[target.lower()]
2024-12-09 18:27:40 -08:00
if target in channels_list:
if pending in channels_list[target]:
for i in channels_list[channel]:
try:
if i != pending:
nickname_list[i].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
except:
pass
elif target in nickname_list:
nickname_list[target].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
else:
connection.sendall(bytes(f":{server} 401 {pending} {target} :No such nick/channel\r\n","UTF-8"))
2024-12-08 20:02:59 -08:00
else:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 11:07:26 -08:00
elif command == "QUIT":
2024-12-08 20:02:59 -08:00
# Parse the quit message.
done = []
msg = text.split(" ")[1:]
2024-12-17 14:07:33 -08:00
if msg[0] == ":":
msg=msg[1:]
2024-12-08 20:02:59 -08:00
if len(msg) > 0:
mse = " ".join(msg)
msg = f"Quit: {mse}"
else:
2024-12-09 15:33:56 -08:00
msg = "Quit: " + pending
2024-12-08 20:02:59 -08:00
text = f"QUIT :{msg}"
2024-12-08 20:21:29 -08:00
# Broadcast all users in the joined channels that the person quit.
2024-12-08 20:02:59 -08:00
for i, users in channels_list.items():
if pending in users:
for j in users:
if j != pending and not j in done:
2024-12-09 13:26:23 -08:00
nickname_list[j].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
2024-12-08 20:02:59 -08:00
done.append(j)
# Remove the quitting user from the channel.
2024-12-08 16:54:04 -08:00
try:
2024-12-08 20:02:59 -08:00
channels_list[i].remove(pending)
2024-12-08 16:54:04 -08:00
except:
2024-12-08 20:02:59 -08:00
print(traceback.format_exc())
2024-12-08 20:48:30 -08:00
# Confirm QUIT and close the socket.
2024-12-09 13:53:01 -08:00
try:
2024-12-09 13:48:45 -08:00
connection.sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
connection.sendall(bytes(f"ERROR :Closing Link: {hostname} ({msg})\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
finally:
connection.close()
safe_quit = True
2024-12-09 17:29:46 -08:00
break
2024-12-09 19:31:48 -08:00
elif command == "MODE":
2024-12-11 15:12:48 -08:00
target = args[0]
2024-12-09 19:31:48 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 19:31:48 -08:00
elif len(args) == 1:
if args[0] == pending:
yourmodes = property_list[pending]["modes"]
2024-12-11 15:04:03 -08:00
connection.sendall(bytes(f":{server} 221 {pending} +{yourmodes}\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
elif args[0] in channels_list:
if args[0] in property_list:
if "modes" in property_list[args[0]]:
# Get the modes + parameters, then print them out.
modes = property_list[args[0]]["modes"]
params = property_list[args[0]]["params"]
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 221 {pending} {target} +{modes} {params}\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
else:
# Default channel mode
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 324 {pending} {target} +n\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
else:
# Default channel mode
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 324 {pending} {target} +n\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
else:
if args[0][0] == "#":
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 403 {pending} {target} :No such channel\r\n","UTF-8"))
2024-12-09 21:43:11 -08:00
else:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 505 {pending} :Cant change mode for other users\r\n","UTF-8"))
2024-12-09 19:31:48 -08:00
2024-12-11 15:28:52 -08:00
elif command == "GITSERV" or (command == "PRIVMSG" and args[0].lower() == "gitserv"):
if command == "PRIVMSG":
args = args[1:]
2024-12-15 19:10:18 -08:00
if args[0][0] == ":":
args[0] == args[0][1:]
2024-12-09 18:27:40 -08:00
if len(args) == 0:
2024-12-09 21:56:12 -08:00
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
elif args[0].upper() == "PULL":
2024-12-09 18:30:43 -08:00
updater = subprocess.run(["git", "pull"], stdout=subprocess.PIPE)
2024-12-09 18:27:40 -08:00
if updater.stdout.decode().strip() == "Already up to date.":
2024-12-09 18:57:05 -08:00
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :Codename IRCat is already up-to-date.\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
else:
2024-12-09 18:57:05 -08:00
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :Done, it is recommended to use /RESTART if you're an IRC op\r\n","UTF-8"))
2024-12-09 18:27:40 -08:00
elif args[0].upper() == "VERSION":
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :Codename IRCat version {__version__}\r\n","UTF-8"))
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :This is Codename IRCat's integrated services.\r\n","UTF-8"))
else:
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :GitServ Usage:\r\n","UTF-8"))
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :PULL - Pulls the latest version of Codename IRCat\r\n","UTF-8"))
connection.sendall(bytes(f":GitServ!~IRCat@IRCatCore NOTICE {pending} :VERSION - Gets the version number of this service.\r\n","UTF-8"))
2024-12-13 13:03:06 -08:00
elif command == "NICKSERV" or (command == "PRIVMSG" and args[0].lower() == "nickserv"):
if command == "PRIVMSG":
args = args[1:]
if len(args) == 0:
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
elif args[0].upper() == "IDENTIFY":
2024-12-13 13:19:50 -08:00
pass
2024-12-13 13:03:06 -08:00
elif args[0].upper() == "VERSION":
connection.sendall(bytes(f":NickServ!~IRCat@IRCatCore NOTICE {pending} :Codename IRCat version {__version__}\r\n","UTF-8"))
connection.sendall(bytes(f":NickServ!~IRCat@IRCatCore NOTICE {pending} :This is Codename IRCat's integrated services.\r\n","UTF-8"))
else:
connection.sendall(bytes(f":NickServ!~IRCat@IRCatCore NOTICE {pending} :NickServ Usage:\r\n","UTF-8"))
2024-12-13 13:19:50 -08:00
connection.sendall(bytes(f":NickServ!~IRCat@IRCatCore NOTICE {pending} :IDENTIFY - Identifies your nickname\r\n","UTF-8"))
2024-12-13 13:03:06 -08:00
connection.sendall(bytes(f":NickServ!~IRCat@IRCatCore NOTICE {pending} :VERSION - Gets the version number of this service.\r\n","UTF-8"))
2024-12-11 14:50:16 -08:00
2024-12-11 14:55:35 -08:00
elif command == "RESTART":
if "o" in property_list[pending]["modes"]:
tcp_socket.shutdown(socket.SHUT_RDWR)
tcp_socket.close()
2024-12-11 15:22:20 -08:00
global opened
2024-12-11 14:59:32 -08:00
opened = False
2024-12-11 14:55:35 -08:00
else:
connection.sendall(bytes(f":{server} 481 {pending} :Permission Denied- You're not an IRC operator\r\n","UTF-8"))
2024-12-11 14:50:16 -08:00
elif command == "PRIVMSG":
if len(args) >= 2:
target = text.split(" ")[1]
if target.lower() in lower_nicks:
target = lower_nicks[target.lower()]
if target in channels_list:
if pending in channels_list[target]:
for i in channels_list[channel]:
try:
if i != pending:
nickname_list[i].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
except:
pass
elif target in nickname_list:
nickname_list[target].sendall(bytes(f":{pending}!~{username}@{hostname} {text}\r\n","UTF-8"))
else:
connection.sendall(bytes(f":{server} 401 {pending} {target} :No such nick/channel\r\n","UTF-8"))
else:
connection.sendall(bytes(f":{server} 461 {pending} {command} :Not enough parameters\r\n","UTF-8"))
2024-12-09 10:22:43 -08:00
# Ignore empty text
elif text.split(" ")[0] == "":
pass
else:
# Unknown command
cmd = text.split(" ")[0]
connection.sendall(bytes(f":{server} 421 {pending} {cmd} :Unknown command\r\n","UTF-8"))
2024-12-08 21:44:32 -08:00
2024-12-09 14:08:31 -08:00
except Exception as ex:
2024-12-08 16:54:04 -08:00
print(traceback.format_exc())
2024-12-09 14:22:31 -08:00
cause = "Read error: " + str(ex)
2024-12-09 13:53:01 -08:00
break
2024-12-08 16:54:04 -08:00
if not data:
2024-12-09 14:22:31 -08:00
cause = "Remote host closed the connection"
2024-12-08 16:54:04 -08:00
break
finally:
connection.close()
2024-12-15 19:29:27 -08:00
try:
if "cause" in property_list[pending]:
cause = property_list[pending]["cause"]
if pending != "*":
del nickname_list[pending]
del property_list[pending]
del lower_nicks[pending.lower()]
if not safe_quit:
done = []
for i, users in channels_list.items():
if pending in users:
for j in users:
if j != pending and not j in done:
2024-12-16 14:22:14 -08:00
try:
nickname_list[j].sendall(bytes(f":{pending}!~{username}@{hostname} QUIT :{cause}\r\n","UTF-8"))
done.append(j)
except:
print(traceback.format_exc())
2024-12-15 19:29:27 -08:00
# Remove the quitting user from the channel.
try:
channels_list[i].remove(pending)
except:
print(traceback.format_exc())
except:
2024-12-16 14:22:14 -08:00
print(traceback.format_exc())
2024-12-15 19:29:27 -08:00
cleanup_manual()
def cleanup():
while True:
time.sleep(15)
2024-12-15 19:39:41 -08:00
cleanup_manual()
2024-12-15 19:29:27 -08:00
def cleanup_manual():
global channels_list
global property_list
print("Cleaning up...")
2024-12-15 19:39:41 -08:00
for j, i in channels_list.items():
2024-12-15 19:29:27 -08:00
for h in i:
if not h in property_list:
print("Found a detached connection: " + h)
i.remove(h)
for k in channels_list[j]:
if k != h and k in nickname_list:
2024-12-16 09:28:49 -08:00
nickname_list[k].sendall(f":{h}!~DISCONNECTED@DISCONNECTED PART {j} :IRCat Cleanup: Found missing connection!!\r\n")
2024-12-08 16:54:04 -08:00
try:
2024-12-11 14:59:32 -08:00
while opened:
2024-12-12 13:37:45 -08:00
print("Waiting for connection...")
2024-12-08 16:54:04 -08:00
connection, client = tcp_socket.accept()
threading.Thread(target=session, daemon=True, args=[connection, client]).start()
except:
print("Shutting down...")
time.sleep(2)
2024-12-08 17:36:59 -08:00
tcp_socket.shutdown(1)
2024-12-08 16:54:04 -08:00
tcp_socket.close()