commit d02b156ac51a804151ebec050cb8e7e4c8d8c7cc Author: delorean Date: Fri May 26 00:01:17 2023 -0500 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9d5afd --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Swarm +> mega millions irc bot swarm, fork of acidvegas' Jupiter + +## Commands +| Command | Description | +| ----------------------- | --------------------------------------------------------------------------------------------------------- | +| 5000 \ | Emulates SuperNETs #5000 channel *(Joins \ and will PM bomb anyone who joins the channel)* | +| id | Send bot identity | +| raw [-d] \ | Send \ to server, optionally delayed with -d argument | +| relay \<+/->\ | Add (+) or Remove (-) \ from relay list *(relays all \ messages to hub channel)* | +| relay -all | Stops all relays | +| monitor list | Return MONITOR list | +| monitor reset | Reset MONITOR list | +| monitor \<+/->\ | Add (+) or Remove (-) \ from MONITOR list. *(Can be a single nick or comma seperated list)* | +| cover \<+/->\ | Add (+) or Remove (-) \ from kick protection *(Bot will kick anyone who kicks \)* | +| admin \<+/->\ | Add (+) or Remove (-) \ from Admin privileges | +| auto-op | Toggles auto-op'ing for admins | + + +**Note:** All commands must be prefixed with @all or the bots nick & will work in a channel or private message. + +Raw data must be IRC RFC compliant data & any nicks in the MONITOR list will be juped as soon as they become available. + diff --git a/swarm.py b/swarm.py new file mode 100644 index 0000000..f78e755 --- /dev/null +++ b/swarm.py @@ -0,0 +1,423 @@ +#!/usr/bin/env python3 + +# Initially 'jupiter' by acidvegas +# Furthered by delorean +# +# irc.supernets.org #superbowl + +import random +import re +import socket +import ssl +import time +import threading + +# Servers +servers = ( + {'server':'efnet.deic.eu', 'ssl':6697, 'ipv6': True}, + {'server':'efnet.port80.se', 'ssl':6697, 'ipv6': True}, + #{'server':'efnet.portlane.se', 'ssl':6697, 'ipv6': True}, # Removed (efnet.portlane.se is an alias for irc.efnet.org) + {'server':'irc.choopa.net', 'ssl':9000, 'ipv6': True}, + {'server':'irc.colosolutions.net', 'ssl':None, 'ipv6':False}, # error: SSL handshake failed: unsafe legacy renegotiation disabled + {'server':'irc.du.se', 'ssl':None, 'ipv6':False}, # error: handshake failed: dh key too small + #{'server':'irc.efnet.fr', 'ssl':6697, 'ipv6': True}, # Removed (irc.efnet.fr is an alias for irc.efnet.nl) + {'server':'irc.efnet.nl', 'ssl':6697, 'ipv6': True}, + {'server':'irc.homelien.no', 'ssl':6697, 'ipv6': True}, + {'server':'irc.mzima.net', 'ssl':6697, 'ipv6': True}, + #{'server':'irc.nordunet.se', 'ssl':6697, 'ipv6': True}, # Removed (irc.nordunet.se is an alias for irc.swepipe.se) + {'server':'irc.prison.net', 'ssl':None, 'ipv6':False}, + {'server':'irc.swepipe.se', 'ssl':6697, 'ipv6': True}, + {'server':'irc.underworld.no', 'ssl':6697, 'ipv6': True}, + {'server':'irc.servercentral.net', 'ssl':9999, 'ipv6':False} +) + +ipv6 = True # Set to False if your system does not have an IPv6 address +channel = "#nword" +key = "$$$" + +# Settings +admin = ["*rean!*@*"] # Can use wildcards (Must be in nick!user@host format) +concurrency = 4 # Number of clones to load per server +id = "dev" # Unique ID so you can tell which bots belong what server + +# Formatting Control Characters / Color Codes +bold = "\x02" +reset = "\x0f" +green = "03" +red = "04" +purple = "06" +orange = "07" +yellow = "08" +light_green = "09" +cyan = "10" +light_cyan = "11" +light_blue = "12" +pink = "13" +grey = "14" + +# Globals +bots = list() + +def botlist(nick): + global bots + if nick[:1] == "+": + bots.append(nick[1:]) + elif nick[:1] == "-": + bots.remove(nick[1:]) + +def color(msg, foreground, background=None): + return (f"\x03{foreground},{background}{msg}{reset}" + if background + else f"\x03{foreground}{msg}{reset}" + ) + +def debug(msg): + print(f"{get_time()} | [~] - {msg}") + +def error(msg, reason=None): + print(f"{get_time()} | [!] - {msg} ({reason})") if reason else print(f"{get_time()} | [!] - {msg}") + +def get_time(): + return time.strftime("%I:%M:%S") + +def is_admin(ident): + for i in admin: + if re.compile(i.replace('*','.*')).search(ident): + return True + return False + +def random_nick(): + prefix = random.choice(['st','sn','cr','pl','pr','fr','fl','qu','br','gr','sh','sk','tr','kl','wr','bl']+list('bcdfgklmnprstvwz')) + midfix = random.choice(('aeiou'))+random.choice(('aeiou'))+random.choice(('bcdfgklmnprstvwz')) + suffix = random.choice(['ed','est','er','le','ly','y','ies','iest','ian','ion','est','ing','led','inger']+list('abcdfgklmnprstvwz')) + return prefix+midfix+suffix + +def unicode(): + msg='\u202e\u0007\x03' + str(random.randint(2,14)) + for i in range(random.randint(150, 200)): + msg += chr(random.randint(0x1000,0x3000)) + return msg + +class clone(threading.Thread): + def __init__(self, server, addr_type): + self.addr_type = addr_type + self.landmine = None + self.nickname = random_nick() + self.monlist = list() + self.relaylist = list() + self.coverlist = list() + self.autoop = False + self.server = server + self.port = 6667 + self.relay = None + self.sock = None + self.ssl_status = None + threading.Thread.__init__(self) + + def run(self): + time.sleep(random.randint(300, 900)) + self.connect() + + def connect(self): + try: + self.create_socket() + self.sock.connect((self.server["server"], self.port)) + self.raw(f"USER {random_nick()} 0 * :{random_nick()}") + self.nick(self.nickname) + except socket.error as ex: + error("Failed to connect to '{0}' IRC server.".format(self.server["server"]), ex) + self.event_disconnect() + except ssl.SSLError as ex: + error("Failed to connect to '{0}' IRC server using SSL/TLS.".format(self.server["server"]), ex) + self.ssl_status = False + self.event_disconnect() + else: + self.listen() + + def create_socket(self): + self.sock = socket.socket(self.addr_type) + if self.server["ssl"]: + if self.ssl_status: + self.port = self.server["ssl"] + self.sock = ssl.wrap_socket(self.sock) + else: + self.port = 6667 + self.ssl_status = True + + def event_connect(self): + if self.nickname not in bots: + botlist("+" + self.nickname) + if self.monlist: + self.monitor("+", self.monlist) + self.join_channel(channel, key) + + def event_ctcp(self, nick, target, msg): + if target == self.nickname: + if msg == 'VERSION': + version = random.choice('Wal-Mart IRC Client','mIRC v6.35 Khaled Mardam-Bey','xchat 0.24.1 Linux 2.6.27-8-eeepc i686','rZNC Version 1.0 [02/01/11] - Built from ZNC','thelounge v3.0.0 -- https://thelounge.chat/') + self.raw(f'NOTICE {nick} \001VERSION {version}\001') + else: + self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('CTCP', green), color('<', grey), color(nick, yellow), color('>', grey), msg)) + + def event_disconnect(self): + if self.nickname in bots: + botlist("-" + self.nickname) + self.sock.close() + time.sleep(86400 + random.randint(1800, 3600)) + self.connect() + + def event_join(self, ident, nick, chan): + if is_admin(ident) and self.autoop: + self.raw("MODE {0} +o {1}".format(chan, nick)) + if chan == self.landmine: + for x in range(0, 6): + self.sendmsg(chan, f"{unicode()} oh god {nick} what is happening {unicode()}") + self.sendmsg(nick, f"{unicode()} oh god {nick} what is happening {unicode()}") + + def event_nick(self, nick, new_nick): + if nick == self.nickname: + self.nickname = new_nick + if new_nick in self.monlist: + self.monitor("C") + self.monlist = list() + elif nick in self.monlist: + self.nick(nick) + + def event_nick_in_use(self, nick, target_nick): + if nick == "*": + self.nickname = random_nick() + self.nick(self.nickname) + + def event_notice(self, nick, target, msg): + if target == self.nickname: + self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('NOTICE', purple), color('<', grey), color(nick, yellow), color('>', grey), msg)) + + def event_message(self, ident, nick, target, msg): + if target.lower() in self.relaylist: + self.sendmsg(channel, "[{0}] <{1}> {2}".format(color(target, light_cyan), color(nick, purple), msg)) + if is_admin(ident): + args = msg.split() + if args[0] in ("@all", self.nickname) and len(args) >= 2: + if len(args) == 2: + if args[1] == "id": + self.sendmsg(target, id) + elif len(args) == 3: + if args[1] == "5000": + chan = args[2] + if chan == "stop": + self.landmine = None + self.sendmsg(target, "5000 mode turned off") + elif chan[:1] == "#": + self.landmine = chan + self.sendmsg(target, "5000 mode actived on " + color(chan, cyan)) + elif args[1] == 'migrate': + if args[2][:1] == '#': + channel = args[2] + self.sendmsg(target, 'hub migrated to {0}'.format(color(args[2], light_green))) + elif args[1] == 'auto-op': + if args[2][:2].lower() == 'on': + self.autoop = True + self.sendmsg(channel, 'auto opping {0}'.format(color("enabled", light_green))) + elif args[2][:3].lower() == 'off': + self.autoop = False + self.sendmsg(channel, 'auto opping {0}'.format(color(args[2][1:], red))) + elif args[1] == 'admin': + adstr = args[2][1:] + "!*@*" + if args[2][:1] == '+': + if adstr in admin: + self.sendmsg(target, '{0}'.format(color('user is already an admin', red))) + else: + admin.append(adstr) + self.sendmsg(target, '{0} promoted to {1}'.format(color(args[2][1:], light_green), color("admin", light_green))) + elif args[2][:1] == '-': + if adstr not in admin: + self.sendmsg(target, '{0}'.format(color('user was not an admin', red))) + else: + admin.remove(adstr) + self.sendmsg(target, '{0} admin {1}'.format(color('removed', red), color(args[2][1:], red))) + elif args[1] == 'monitor': + if args[2] == 'list' and self.monlist: + self.sendmsg(target, '[{0}] {1}'.format(color('Monitor', light_blue), ', '.join(self.monlist))) + elif args[2] == 'reset' and self.monlist: + self.monitor('C') + self.monlist = list() + self.sendmsg(target, '{0} nick(s) have been {1} from the monitor list.'.format(color(str(len(self.monlist)), cyan), color('removed', red))) + elif args[2][:1] == '+': + nicks = [mon_nick for mon_nick in set(args[2][1:].split(',')) if mon_nick not in self.monlist] + if nicks: + self.monitor('+', nicks) + self.monlist += nicks + self.sendmsg(target, '{0} nick(s) have been {1} to the monitor list.'.format(color(str(len(nicks)), cyan), color('added', green))) + elif args[2][:1] == '-': + nicks = [mon_nick for mon_nick in set(args[2][1:].split(',')) if mon_nick in self.monlist] + if nicks: + self.monitor('-', nicks) + for mon_nick in nicks: + self.monlist.remove(mon_nick) + self.sendmsg(target, '{0} nick(s) have been {1} from the monitor list.'.format(color(str(len(nicks)), cyan), color('removed', red))) + elif args[1] == 'relay': + if args[2][:1] == '+': + if args[2][1:].lower() in self.relaylist: + self.sendmsg(channel, '{0}'.format(color('already snitching on that channel', red))) + else: + self.relaylist.append(args[2][1:].lower()) + self.sendmsg(channel, '{0} on {1}'.format(color('snitching', red), color(args[2][1:], red))) + elif args[2][:1] == '-': + if args[2][1:].lower() == 'all': + self.sendmsg(channel, '{0} on {1} {2}'.format(color('ceased snitching', red), color('all', yellow), color('channels', red))) + self.relaylist.clear() + elif args[2][1:].lower() not in self.relaylist: + self.sendmsg(channel, '{0}'.format(color('channel was not being snitched on', red))) + else: + self.relaylist.remove(args[2][1:].lower()) + self.sendmsg(channel, '{0} on {1}'.format(color('ceased all snitching', red), color(args[2][1:], red))) + elif args[1] == 'cover': + if args[2][:1] == '+': + if args[2][1:].lower() in self.coverlist: + self.sendmsg(channel, '{0}'.format(color('already watching that nigga\'s back', red))) + else: + self.coverlist.append(args[2][1:].lower()) + self.sendmsg(channel, 'got your back, {0}'.format(color(args[2][1:], light_green))) + elif args[2][:1] == '-': + if args[2][1:].lower() == 'all': + self.sendmsg(channel, '{0} {1} {2}'.format(color('ceased covering', red), color('all', grey), color('muhfuckas', red))) + self.coverlist.clear() + elif args[2][1:].lower() not in self.coverlist: + self.sendmsg(channel, '{0}'.format(color('didnt have their back in the first place', red))) + else: + self.coverlist.remove(args[2][1:].lower()) + self.sendmsg(channel, 'no longer watching {0}\'s back'.format(color(args[2][1:], light_green))) + elif len(args) >= 4 and args[1] == "raw": + if args[2] == "-d": + data = " ".join(args[3:]) + threading.Thread(target=self.raw, args=(data, True)).start() + else: + data = " ".join(args[2:]) + self.raw(data) + + elif target == self.nickname: + if msg.startswith('\x01ACTION'): + self.sendmsg(channel, '[{0}] {1}{2}{3} * {4}'.format(color('PM', red), color('<', grey), color(nick, yellow), color('>', grey), msg[8:][:-1])) + else: + self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('PM', red), color('<', grey), color(nick, yellow), color('>', grey), msg)) + + def event_kick(self, ident, nick, chan, target): + if target == self.nickname: + self.sendmsg(channel, "{0} from {1} by {2} {3}".format(color("kicked", yellow), color(chan, red), color(nick, yellow), color(";(", white))) + elif target.lower() in self.coverlist and not is_admin(ident): + self.raw("KICK {0} {1} :that shit outta here".format(chan, nick)) + + def event_mode(self, nick, chan, modes): + # Todo: takeover mode to automatically jupe a channel upon +o + pass + + def event_quit(self, nick): + if nick in self.monlist: + self.nick(nick) + + def handle_events(self, data): + args = data.split() + if data.startswith("ERROR :Closing Link:"): + raise Exception("Connection has closed.") + elif data.startswith("ERROR :Reconnecting too fast"): + raise Exception("Connection has closed. (throttled)") + elif args[0] == "PING": + self.raw("PONG " + args[1][1:]) + elif args[1] == "001": # RPL_WELCOME + self.event_connect() + elif args[1] == "433" and len(args) >= 4: # ERR_NICKNAMEINUSE + nick = args[2] + target_nick = args[3] + self.event_nick_in_use(nick, target_nick) + elif args[1] == "731" and len(args) >= 4: # RPL_MONOFFLINE + nick = args[3][1:] + self.nick(nick) + elif args[1] == "JOIN" and len(args) == 3: + ident = args[0][1:] + nick = args[0].split("!")[0][1:] + chan = args[2][1:] + self.event_join(ident, nick, chan) + elif args[1] == "MODE" and len(args) >= 4: + nick = args[0].split("!")[0][1:] + chan = args[2] + modes = " ".join(args[:3]) + self.event_mode(nick, chan, modes) + elif args[1] == "NICK" and len(args) == 3: + nick = args[0].split("!")[0][1:] + new_nick = args[2][1:] + self.event_nick(nick, new_nick) + elif args[1] == "NOTICE": + nick = args[0].split("!")[0][1:] + target = args[2] + msg = " ".join(args[3:])[1:] + self.event_notice(nick, target, msg) + elif args[1] == "PRIVMSG" and len(args) >= 4: + ident = args[0][1:] + nick = args[0].split("!")[0][1:] + target = args[2] + msg = " ".join(args[3:])[1:] + if msg[:1] == "\001": + msg = msg[1:] + self.event_ctcp(nick, target, msg) + else: + self.event_message(ident, nick, target, msg) + elif args[1] == "QUIT": + nick = args[0].split("!")[0][1:] + self.event_quit(nick) + elif args[1] == "KICK": + ident = args[0][1:] + nick = args[0].split("!")[0][1:] + chan = args[2] + target = args[3] + self.event_kick(ident, nick, chan, target) + + def join_channel(self, chan, key=None): + self.raw(f"JOIN {chan} {key}") if key else self.raw("JOIN " + chan) + + def listen(self): + while True: + try: + data = self.sock.recv(1024).decode("utf-8") + for line in (line for line in data.split("\r\n") if len(line.split()) >= 2): + # debug(line) + self.handle_events(line) + except (UnicodeDecodeError, UnicodeEncodeError): + pass + except Exception as ex: + error("Unexpected error occured on '{0}' server.".format(self.server["server"]),ex) + break + self.event_disconnect() + + def mode(self, target, mode): + self.raw(f"MODE {target} {mode}") + + def monitor(self, action, nicks=list()): + self.raw(f"MONITOR {action} " + ",".join(nicks)) + + def nick(self, nick): + self.raw("NICK " + nick) + + def raw(self, data, delay=False): + if delay: + time.sleep(random.randint(300, 900)) + self.sock.send(bytes(data + "\r\n", "utf-8")) + + def sendmsg(self, target, msg): + self.raw(f"PRIVMSG {target} :{msg}") + + +# Main +for i in range(concurrency): + for server in servers: + clone(server, socket.AF_INET).start() + if ipv6: + if set( + [ + ip[4][0] + for ip in socket.getaddrinfo(server["server"], 6667) + if ":" in ip[4][0] + ] + ): + clone(server, socket.AF_INET6).start() +while True: + input("")