archive

- Random tools & helpful resources for IRC
git clone git://git.acid.vegas/archive.git
Log | Files | Refs | Archive

limitserv.py (7985B)

      1 #!/usr/bin/env python
      2 # LimitServ IRC Service Bot - Developed by acidvegas in Python (https://acid.vegas/random)
      3 
      4 import socket
      5 import threading
      6 import time
      7 
      8 # Configuration
      9 _connection = {'server':'irc.server.com', 'port':6697, 'ssl':True, 'ssl_verify':False, 'ipv6':False, 'vhost':None}
     10 _cert       = {'file':None, 'key':None, 'password':None}
     11 _ident      = {'nickname':'LimitServ', 'username':'services', 'realname':'Channel Limit Service'}
     12 _login      = {'nickserv':'fartsimpson29', 'network':None, 'operator':'simps0nsfan69'}
     13 _throttle   = {'limit':300, 'queue':0.5, 'voice':10}
     14 _settings   = {'anope':False, 'honeypot':'#blackhole', 'limit':10, 'modes':None}
     15 
     16 def debug(msg):
     17 	print(f'{get_time()} | [~] - {msg}')
     18 
     19 def error(msg, reason):
     20 	print(f'{get_time()} | [!] - {msg} ({reason})')
     21 
     22 def get_time():
     23 	return time.strftime('%I:%M:%S')
     24 
     25 class IRC(object):
     26 	def __init__(self):
     27 		self._channels = list()
     28 		self._names = list()
     29 		self._queue = list()
     30 		self._voices = dict()
     31 		self._sock = None
     32 
     33 	def _run(self):
     34 		Loop._loops()
     35 		self._connect()
     36 
     37 	def _connect(self):
     38 		try:
     39 			self._create_socket()
     40 			self._sock.connect((_connection['server'], _connection['port']))
     41 			self._register()
     42 		except socket.error as ex:
     43 			error('Failed to connect to IRC server.', ex)
     44 			Event._disconnect()
     45 		else:
     46 			self._listen()
     47 
     48 	def _create_socket(self):
     49 		self._sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) if _connection['ipv6'] else socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     50 		if _connection['vhost']:
     51 			self._sock.bind((_connection['vhost'], 0))
     52 		if _connection['ssl']:
     53 			ctx = ssl.SSLContext()
     54 			if _cert['file']:
     55 				ctx.load_cert_chain(_cert['file'], _cert['key'], _cert['password'])
     56 			if _connection['ssl_verify']:
     57 				ctx.verify_mode = ssl.CERT_REQUIRED
     58 				ctx.load_default_certs()
     59 			else:
     60 				ctx.check_hostname = False
     61 				ctx.verify_mode = ssl.CERT_NONE
     62 			self._sock = ctx.wrap_socket(self._sock)
     63 
     64 	def _listen(self):
     65 		while True:
     66 			try:
     67 				data = self._sock.recv(1024).decode('utf-8')
     68 				for line in (line for line in data.split('\r\n') if len(line.split()) >= 2):
     69 					debug(line)
     70 					Event._handle(line)
     71 			except (UnicodeDecodeError,UnicodeEncodeError):
     72 				pass
     73 			except Exception as ex:
     74 				error('Unexpected error occured.', ex)
     75 				break
     76 		Event._disconnect()
     77 
     78 	def _register(self):
     79 		if _login['network']:
     80 			Bot._queue.append('PASS ' + _login['network'])
     81 		Bot._queue.append('USER {0} 0 * :{1}'.format(_ident['username'], _ident['realname']))
     82 		Bot._queue.append('NICK ' + _ident['nickname'])
     83 
     84 class Command:
     85 	def _join(chan, key=None):
     86 		Bot._queue.append('JOIN {chan} {key}') if key else Bot._queue.append('JOIN ' + chan)
     87 
     88 	def _kick(chan, nick, msg=None):
     89 		Bot._queue.append(f'KICK {chan} {nick} {msg}') if msg else Bot._queue.append(f'KICK {chan} {nick}')
     90 
     91 	def _mode(target, mode):
     92 		Bot._queue.append(f'MODE {target} {mode}')
     93 
     94 	def _raw(msg):
     95 		Bot._sock.send(bytes(msg[:510] + '\r\n', 'utf-8'))
     96 
     97 	def _sendmsg(target, msg):
     98 		Bot._queue.append(f'PRIVMSG {target} :{msg}')
     99 
    100 class Event:
    101 	def _connect():
    102 		if _settings['modes']:
    103 			Command._mode(_ident['nickname'], '+' + _settings['modes'])
    104 		if _login['nickserv']:
    105 			Command._sendmsg('NickServ', 'IDENTIFY {0} {1}'.format(_ident['nickname'], _login['nickserv']))
    106 		if _login['operator']:
    107 			Bot._queue.append('OPER {0} {1}'.format(_ident['username'], _login['operator']))
    108 
    109 	def _disconnect():
    110 		Bot._sock.close()
    111 		Bot._names = list()
    112 		Bot._queue = list()
    113 		Bot._voices = dict()
    114 		time.sleep(15)
    115 		Bot.connect()
    116 
    117 	def _end_of_names(chan):
    118 		limit = str(len(Bot._names) + _settings['limit'])
    119 		if settings['anope']:
    120 			Command._sendmsg('ChanServ', 'MODE {0} LOCK ADD +lL {1} {2}'.format(chan, limit, _settings['honeypot']))
    121 		else:
    122 			Command._mode(chan, '+lL {0} {1}'.format(limit, _settings['honeypot']))
    123 		Bot._names = list()
    124 
    125 	def _join(nick, chan):
    126 		if nick == _ident['nickname'].lower():
    127 			if chan not in Bot._channels:
    128 				Bot._channels.append(chan)
    129 			if chan not in Bot._voices:
    130 				Bot._voices[chan] = dict()
    131 		elif chan in Bot._channels:
    132 			if nick not in Bot._voices[chan]:
    133 				Bot._voices[chan][nick] = time.time()
    134 
    135 	def _kick(nick, chan, kicked):
    136 		if nick == _ident['nickname'].lower():
    137 			Bot._channels.remove(chan)
    138 			del Bot._voices[chan]
    139 			time.sleep(3)
    140 			Command._join(chan)
    141 		elif chan in Bot._channels:
    142 			if nick in Bot._voices[chan]:
    143 				del Bot._voices[chan][nick]
    144 
    145 	def _names(chan, nicks):
    146 		for name in nicks:
    147 			if name[:1] in '~!@%&+':
    148 				name = name[1:]
    149 			Bot._names.append(name)
    150 
    151 	def _nick(nick, new_nick):
    152 		for chan in Bot._voices:
    153 			if nick in Bot._voices[chan]:
    154 				Bot._voices[chan][new_nick] = Bot._voices[chan][nick]
    155 				del Bot._voices[chan][nick]
    156 
    157 	def _no_such_nick(nick):
    158 		for chan in Bot._voices:
    159 			if nick in Bot._voices[chan]:
    160 				del Bot.voices[chan][nick]
    161 
    162 	def _part(nick, chan):
    163 		if nick == _ident['nickname'].lower():
    164 			Bot._channels.remove(chan)
    165 			del Bot._voices[chan]
    166 		elif chan in Bot._channels:
    167 			if nick in Bot._voices[chan]:
    168 				del Bot._voices[chan][nick]
    169 
    170 	def _quit(nick):
    171 		for chan in Bot._voices:
    172 			if nick in Bot._voices[chan]:
    173 				del Bot._voices[chan][nick]
    174 
    175 	def _handle(data):
    176 		args = data.split()
    177 		if data.startswith('ERROR :Closing Link:'):
    178 			raise Exception('Connection has closed.')
    179 		elif data.startswith('ERROR :Reconnecting too fast, throttled.'):
    180 			raise Exception('Connection has closed. (throttled)')
    181 		elif args[0] == 'PING':
    182 			Command._raw('PONG ' + args[1][1:])
    183 		elif args[1] == '001': # RPL_WELCOME
    184 			Event._connect()
    185 		elif args[1] == '401': # ERR_NOSUCHNICK
    186 			nick = args[3].lower()
    187 			Event._no_such_nick(nick)
    188 		elif args[1] == '433': # ERR_NICKNAMEINUSE
    189 			raise Exception('Bot is already running or nick is in use.')
    190 		elif args[1] == '353' and len(args) >= 6: #RPL_NAMREPLY
    191 			chan = args[4].lower()
    192 			names = ' '.join(args[5:]).lower()[1:].split()
    193 			Event._names(chan, names)
    194 		elif args[1] == '366' and len(args) >= 4: # RPL_ENDOFNAMES
    195 			chan = args[3].lower()
    196 			Event._end_of_names(chan)
    197 		elif args[1] == 'JOIN' and len(args) == 3:
    198 			nick = args[0].split('!')[0][1:].lower()
    199 			chan = args[2][1:].lower()
    200 			Event._join(nick, chan)
    201 		elif args[1] == 'KICK' and len(args) >= 4:
    202 			nick = args[0].split('!')[0][1:].lower()
    203 			chan = args[2].lower()
    204 			kicked = args[3].lower()
    205 			Event._kick(nick, chan, kicked)
    206 		elif args[1] == 'NICK' and len(args) == 3:
    207 			nick = args[0].split('!')[0][1:].lower()
    208 			new_nick = args[2][1:].lower()
    209 			Event._nick(nick, new_nick)
    210 		elif args[1] == 'PART' and len(args) >= 3:
    211 			nick = args[0].split('!')[0][1:].lower()
    212 			chan = args[2].lower()
    213 			Event._part(nick, chan)
    214 		elif args[1] == 'QUIT':
    215 			nick = args[0].split('!')[0][1:].lower()
    216 			Event._quit(nick)
    217 
    218 class Loop:
    219 	def _loops():
    220 		threading.Thread(target=Loop._queue).start() # start first to handle incoming data
    221 		threading.Thread(target=Loop._limit).start()
    222 		threading.Thread(target=Loop._voice).start()
    223 
    224 	def _limit():
    225 		while True:
    226 			try:
    227 				for chan in Bot._channels:
    228 					Bot._queue.append('NAMES ' + chan)
    229 			except Exception as ex:
    230 				error('Error occured in the loop!', ex)
    231 			finally:
    232 				time.sleep(_throttle['limit'])
    233 
    234 	def _queue():
    235 		while True:
    236 			try:
    237 				if Bot._queue:
    238 					Command._raw(Bot._queue.pop(0))
    239 			except Exception as ex:
    240 				error('Error occured in the queue loop!', ex)
    241 			finally:
    242 				time.sleep(_throttle['queue'])
    243 
    244 	def _voice():
    245 		while True:
    246 			try:
    247 				for chan in Bot._voices:
    248 					nicks = [nick for nick in Bot._voices[chan] if time.time() - Bot._voices[chan][nick] > _throttle['voice']]
    249 					for item in [nicks[i:i + 4] for i in range(0, len(nicks), 4)]:
    250 						Command._mode(chan, '+{0} {1}'.format('v'*len(item), ' '.join(item)))
    251 						for subitem in item:
    252 							del Bot._voices[chan][subitem]
    253 			except Exception as ex:
    254 				error('Error occured in the voice loop!', ex)
    255 			finally:
    256 				time.sleep(1)
    257 
    258 # Main
    259 if _connection['ssl']:
    260 	import ssl
    261 else:
    262 	del cert, _connection['verify']
    263 Bot = IRC()
    264 Bot._run()