skeleton

- bot skeleton for irc
git clone git://git.acid.vegas/skeleton.git
Log | Files | Refs | Archive | README | LICENSE

commit 35d810d89b29a782ffb65093c6b65f600825652c
parent 825b549153686c81792ed29a25260d7f63ca38bb
Author: acidvegas <acid.vegas@acid.vegas>
Date: Mon, 3 May 2021 18:49:03 -0400

Updated to be asyncronous and cleaned up source majorly

Diffstat:
MLICENSE | 5++---
MREADME.md | 15+++++++++++----
Aadvanced/core/bot.py | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aadvanced/core/commands.py | 44++++++++++++++++++++++++++++++++++++++++++++
Aadvanced/core/config.py | 40++++++++++++++++++++++++++++++++++++++++
Aadvanced/core/events.py | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aadvanced/skeleton.py | 41+++++++++++++++++++++++++++++++++++++++++
Mskeleton.py | 425++++++++++++++++++++++++++++++-------------------------------------------------
Dskeleton/core/config.py | 40----------------------------------------
Dskeleton/core/constants.py | 229-------------------------------------------------------------------------------
Dskeleton/core/database.py | 35-----------------------------------
Dskeleton/core/debug.py | 81-------------------------------------------------------------------------------
Dskeleton/core/functions.py | 10----------
Dskeleton/core/irc.py | 289------------------------------------------------------------------------------
Dskeleton/data/cert/.gitignore | 5-----
Dskeleton/data/logs/.gitignore | 5-----
Dskeleton/modules/.gitignore | 5-----
Dskeleton/skeleton.py | 24------------------------

18 files changed, 417 insertions(+), 992 deletions(-)

diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,6 @@
 ISC License
 
-Copyright (c) 2019, acidvegas <acid.vegas@acid.vegas>
+Copyright (c) 2021, acidvegas <acid.vegas@acid.vegas>
 
 Permission to use, copy, modify, and/or distribute this software for any
 purpose with or without fee is hereby granted, provided that the above
@@ -12,4 +12,4 @@ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-\ No newline at end of file
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
@@ -1,12 +1,19 @@
-###### Requirements
+# skeleton
+> asyncronous bot skeleton for the internet relay chat protocol
+
+## Requirements
 * [Python](https://www.python.org/downloads/) *(**Note:** This script was developed to be used with the latest version of Python)*
 * [PySocks](https://pypi.python.org/pypi/PySocks) *(**Optional:** For using the `proxy` setting)*
 
-###### IRC RCF Reference
+## Information
+The repository comes with 2 skeletons. A simple, single-file skeleton for basic bots & an advanced structured skeleton for more complex bots.
+
+This is just a basic structure to help setup a bot. The bots have no use by default. It is asyncronous, can log to file, handle basic I/O, flood control, etc.
+
+## IRC RCF Reference
 - http://www.irchelp.org/protocol/rfc/
 
-###### Mirrors
+## Mirrors
 - [acid.vegas](https://acid.vegas/skeleton) *(main)*
-- [SuperNETs](https://git.supernets.org/acidvegas/skeleton)
 - [GitHub](https://github.com/acidvegas/skeleton)
 - [GitLab](https://gitlab.com/acidvegas/skeleton)
 \ No newline at end of file
diff --git a/advanced/core/bot.py b/advanced/core/bot.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Asyncronous IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
+# bot.py
+
+import asyncio
+import logging
+
+import config
+
+from commands import Command
+from events import Event
+
+def ssl_ctx():
+	import ssl
+	ctx = ssl.create_default_context()
+	if not config.connection.ssl_verify:
+		ctx.check_hostname = False
+		ctx.verify_mode = ssl.CERT_NONE
+	if config.cert.file:
+	    ctx.load_cert_chain(config.cert.file, password=config.cert.password)
+	return ctx
+
+class IrcBot:
+	def __init__(self):
+		self.options = {
+			'host'       : config.connection.server,
+			'port'       : config.connection.port,
+			'limit'      : 1024,
+			'ssl'        : ssl_ctx() if config.connection.ssl else None,
+			'family'     : 10 if config.connection.ipv6 else 2,
+			'local_addr' : (config.connection.vhost, 0) if config.connection.vhost else None
+		}
+		self.reader = None
+		self.writer = None
+
+	async def run(self):
+		try:
+			self.reader, self.writer = await asyncio.open_connection(**self.options, timeout=config.throttle.timeout)
+		except Exception as ex:
+			logging.exception('Failed to connect to IRC server!')
+		else:
+			try:
+				await Command(Bot).register(config.ident.nickname, config.ident.username, config.ident.realname, config.login.network)
+				while not self.reader.at_eof():
+					data = await self.reader.readline()
+					Event(Bot).handle(data.decode('utf-8').strip())
+			except (UnicodeDecodeError, UnicodeEncodeError):
+				pass
+			except Exception as ex:
+				logging.exception('Unknown error has occured!')
+		finally:
+			Event.disconnect()
+
+Bot = IrcBot()
+\ No newline at end of file
diff --git a/advanced/core/commands.py b/advanced/core/commands.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# Asyncronous IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
+# commands.py
+
+class Command:
+	def __init__(self, bot):
+		self.Bot = bot
+
+	def action(self, target, msg):
+		self.sendmsg(target, f'\x01ACTION {msg}\x01')
+
+	def join_channel(self, chan, key=None):
+		self.raw(f'JOIN {chan} {key}') if key else raw('JOIN ' + chan)
+
+	def mode(self, target, mode):
+		self.raw(f'MODE {target} {mode}')
+
+	def nick(self, new_nick):
+		self.raw('NICK ' + new_nick)
+
+	def notice(self, target, msg):
+		self.raw(f'NOTICE {target} :{msg}')
+
+	def part_channel(self, chan, msg=None):
+		self.raw(f'PART {chan} {msg}') if msg else raw('PART ' + chan)
+
+	def quit(self, msg=None):
+		self.raw('QUIT :' + msg) if msg else raw('QUIT')
+
+	def raw(self, data):
+		self.Bot.writer.write(data[:510].encode('utf-8') + b'\r\n')
+
+	def register(self, nickname, username, realname, password=None):
+		if password:
+			self.raw('PASS ' + password)
+		self.raw('NICK ' + nickname)
+		self.raw(f'USER {username} 0 * :{realname}')
+
+	def sendmsg(self, target, msg):
+		self.raw(f'PRIVMSG {target} :{msg}')
+
+	def topic(self, chan, data):
+		self.raw(f'TOPIC {chan} :{text}')
+\ No newline at end of file
diff --git a/advanced/core/config.py b/advanced/core/config.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# Asyncronous IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
+# config.py
+
+class connection:
+	server        = 'irc.server.com'
+	port          = 6667
+	ipv6          = False
+	ssl           = False
+	ssl_verify    = False
+	vhost         = None
+	channel       = '#dev'
+	key           = None
+	modes         = None
+
+class cert:
+	file     = None
+	password = None
+
+class ident:
+	nickname = 'skeleton'
+	username = 'skeleton'
+	realname = 'acid.vegas/skeleton'
+
+class login:
+	network  = None
+	nickserv = None
+	operator = None
+
+class settings:
+	admin = 'nick!user@host' # Must be in nick!user@host format (Wildcards accepted)
+	log   = False
+
+class throttle:
+	command   = 3
+	message   = 0.5
+	reconnect = 15
+	rejoin    = 5
+	timeout   = 15
+\ No newline at end of file
diff --git a/advanced/core/events.py b/advanced/core/events.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# Asyncronous IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/asyncirc)
+# events.py
+
+import asyncio
+import logging
+
+import config
+
+class Event:
+	def __init__(self, bot):
+		self.Bot = bot
+
+	def connect(self):
+		if config.settings.modes:
+			Commands.raw(f'MODE {config.ident.nickname} +{config.settings.modes}')
+		if config.login.nickserv:
+			Commands.sendmsg('NickServ', f'IDENTIFY {config.ident.nickname} {config.login.nickserv}')
+		if config.login.operator:
+			Commands.raw(f'OPER {config.ident.username} {config.login.operator}')
+		Commands.join_channel(config.connection.channel, config.connection.key)
+
+	async def disconnect(self):
+		self.writer.close()
+		await self.writer.wait_closed()
+		asyncio.sleep(config.throttle.reconnect)
+
+	def join_channel(self):
+		pass
+
+	def kick(self):
+		pass
+
+	def invite(self):
+		pass
+
+	def message(self):
+		pass
+
+	def nick_in_use(self):
+		new_nick = 'a' + str(random.randint(1000,9999))
+		Command.nick(new_nick)
+
+	def part_channel(self):
+		pass
+
+	def private_message(self):
+		pass
+
+	def quit(self):
+		pass
+
+	async def handler(self, data):
+		logging.info(data)
+		args = data.split()
+		if args[0] == 'PING':
+			self.raw('PONG ' + args[1][1:])
+		elif args[1] == '001': #RPL_WELCOME
+			self.connect()
+		elif args[1] == '433': #ERR_NICKNAMEINUSE
+			self.nick_in_use()
diff --git a/advanced/skeleton.py b/advanced/skeleton.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# Asyncronous IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
+# skeleton.py
+
+import asyncio
+import logging
+import logging.handlers
+import os
+import sys
+
+sys.dont_write_bytecode = True
+os.chdir(os.path.dirname(__file__) or '.')
+sys.path += ('core','modules')
+
+import config
+
+if not os.path.exists('logs'):
+	os.makedirs('logs')
+sh = logging.StreamHandler()
+sh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(message)s', '%I:%M %p'))
+if config.settings.log:
+	fh = logging.handlers.RotatingFileHandler('logs/debug.log', maxBytes=250000, backupCount=7, encoding='utf-8')
+	fh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(filename)s.%(funcName)s.%(lineno)d | %(message)s', '%Y-%m-%d %I:%M %p'))
+	logging.basicConfig(level=logging.NOTSET, handlers=(sh,fh))
+	del fh
+else:
+	logging.basicConfig(level=logging.NOTSET, handlers=(sh,))
+del sh
+
+print('#'*56)
+print('#{:^54}#'.format(''))
+print('#{:^54}#'.format('Asyncronous IRC Bot Skeleton'))
+print('#{:^54}#'.format('Developed by acidvegas in Python'))
+print('#{:^54}#'.format('https://acid.vegas/skeleton'))
+print('#{:^54}#'.format(''))
+print('#'*56)
+
+from bot import Bot
+
+asyncio.run(Bot.run())
+\ No newline at end of file
diff --git a/skeleton.py b/skeleton.py
@@ -1,276 +1,177 @@
 #!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-
-import socket
+# Asyncronoua IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
+# skeleton.py
+
+import asyncio
+import logging
+import logging.handlers
+import os
+import random
 import time
-import threading
-
-# Configuration
-_connection = {'server':'irc.supernets.org', 'port':6697, 'proxy':None, 'ssl':True, 'ssl_verify':False, 'ipv6':False, 'vhost':None}
-_cert       = {'file':None, 'key':None, 'password':None}
-_ident      = {'nickname':'DevBot', 'username':'dev', 'realname':'acid.vegas/skeleton'}
-_login      = {'nickserv':None, 'network':None, 'operator':None}
-_settings   = {'channel':'#dev', 'key':None, 'modes':None, 'throttle':1}
-
-# Formatting Control Characters / Color Codes
-bold        = '\x02'
-italic      = '\x1D'
-underline   = '\x1F'
-reverse     = '\x16'
-reset       = '\x0f'
-white       = '00'
-black       = '01'
-blue        = '02'
-green       = '03'
-red         = '04'
-brown       = '05'
-purple      = '06'
-orange      = '07'
-yellow      = '08'
-light_green = '09'
-cyan        = '10'
-light_cyan  = '11'
-light_blue  = '12'
-pink        = '13'
-grey        = '14'
-light_grey  = '15'
-
-def color(msg, foreground, background=None):
-	if background:
-		return f'\x03{foreground},{background}{msg}{reset}'
-	else:
-		return f'\x03{foreground}{msg}{reset}'
-
-def debug(msg):
-	print(f'{get_time()} | [~] - {msg}')
-
-def error(msg, reason=None):
-	if reason:
-		print(f'{get_time()} | [!] - {msg} ({reason})')
-	else:
-		print(f'{get_time()} | [!] - {msg}')
-
-def error_exit(msg):
-	raise SystemExit(f'{get_time()} | [!] - {msg}')
-
-def get_time():
-	return time.strftime('%I:%M:%S')
-
-class IRC(object):
-	def __init__(self):
-		self._queue = list()
-		self._sock = None
 
-	def _run(self):
-		Loop._loops()
-		self._connect()
-
-	def _connect(self):
-		try:
-			self._create_socket()
-			self._sock.connect((_connection['server'], _connection['port']))
-			self._register()
-		except socket.error as ex:
-			error('Failed to connect to IRC server.', ex)
-			Event._disconnect()
-		else:
-			self._listen()
-
-	def _create_socket(self):
-		family = socket.AF_INET6 if _connection['ipv6'] else socket.AF_INET
-		if _connection['proxy']:
-			proxy_server, proxy_port = _connection['proxy'].split(':')
-			self._sock = socks.socksocket(family, socket.SOCK_STREAM)
-			self._sock.setblocking(0)
-			self._sock.settimeout(15)
-			self._sock.setproxy(socks.PROXY_TYPE_SOCKS5, proxy_server, int(proxy_port))
-		else:
-			self._sock = socket.socket(family, socket.SOCK_STREAM)
-		if _connection['vhost']:
-			self._sock.bind((_connection['vhost'], 0))
-		if _connection['ssl']:
-			ctx = ssl.SSLContext()
-			if _cert['file']:
-				ctx.load_cert_chain(_cert['file'], _cert['key'], _cert['password'])
-			if _connection['ssl_verify']:
-				ctx.verify_mode = ssl.CERT_REQUIRED
-				ctx.load_default_certs()
-			else:
-				ctx.check_hostname = False
-				ctx.verify_mode = ssl.CERT_NONE
-			self._sock = ctx.wrap_socket(self._sock)
-
-	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)
-					Event._handle(line)
-			except (UnicodeDecodeError,UnicodeEncodeError):
-				pass
-			#except Exception as ex:
-			#	error('Unexpected error occured.', ex)
-			#	break
-		Event._disconnect()
+##################################################
+
+class config:
+	class connection:
+		server     = 'irc.supernets.org'
+		port       = 6697
+		ipv6       = False
+		ssl        = True
+		ssl_verify = False
+		vhost      = None
+		channel    = '#dev'
+		key        = None
+		modes      = None
+
+	class cert:
+		file     = None
+		password = None
+
+	class ident:
+		nickname = 'skeleton'
+		username = 'skeleton'
+		realname = 'acid.vegas/skeleton'
+
+	class login:
+		network  = None
+		nickserv = None
+		operator = None
+
+	class settings:
+		admin = 'nick!user@host' # Must be in nick!user@host format (Wildcards accepted)
+		log   = False
+
+	class throttle:
+		command   = 3
+		message   = 0.5
+		reconnect = 15
+		rejoin    = 5
+		timeout   = 15
+
+##################################################
+
+def ssl_ctx():
+	import ssl
+	ctx = ssl.create_default_context()
+	if not config.connection.ssl_verify:
+		ctx.check_hostname = False
+		ctx.verify_mode = ssl.CERT_NONE
+	if config.cert.file:
+	    ctx.load_cert_chain(config.cert.file, password=config.cert.password)
+	return ctx
 
-	def _register(self):
-		if _login['network']:
-			Bot._queue.append('PASS ' + _login['network'])
-		Bot._queue.append('USER {0} 0 * :{1}'.format(_ident['username'], _ident['realname']))
-		Bot._queue.append('NICK ' + _ident['nickname'])
+##################################################
 
 class Command:
-	def _action(target, msg):
-		Bot._queue.append(chan, f'\x01ACTION {msg}\x01')
-
-	def _ctcp(target, data):
-		Bot._queue.append(target, f'\001{data}\001')
-
-	def _invite(nick, chan):
-		Bot._queue.append(f'INVITE {nick} {chan}')
-
-	def _join(chan, key=None):
-		Bot._queue.append(f'JOIN {chan} {key}') if key else Bot._queue.append('JOIN ' + chan)
+	def join_channel(chan, key=None):
+		Command.raw(f'JOIN {chan} {key}') if key else Command.raw('JOIN ' + chan)
 
-	def _mode(target, mode):
-		Bot._queue.append(f'MODE {target} {mode}')
+	def mode(target, mode):
+		Command.raw(f'MODE {target} {mode}')
 
-	def _nick(nick):
-		Bot._queue.append('NICK ' + nick)
+	def nick(new_nick):
+		Command.raw('NICK ' + new_nick)
 
-	def _notice(target, msg):
-		Bot._queue.append(f'NOTICE {target} :{msg}')
+	def raw(data):
+		Bot.writer.write(data[:510].encode('utf-8') + b'\r\n')
 
-	def _part(chan, msg=None):
-		Bot._queue.append(f'PART {chan} {msg}') if msg else Bot._queue.append('PART ' + chan)
+	def sendmsg(target, msg):
+		Command.raw(f'PRIVMSG {target} :{msg}')
 
-	def _quit(msg=None):
-		Bot._queue.append('QUIT :' + msg) if msg else Bot._queue.append('QUIT')
-
-	def _raw(data):
-		Bot._sock.send(bytes(data[:510] + '\r\n', 'utf-8'))
-
-	def _sendmsg(target, msg):
-		Bot._queue.append(f'PRIVMSG {target} :{msg}')
-
-	def _topic(chan, text):
-		Bot._queue.append(f'TOPIC {chan} :{text}')
+##################################################
 
 class Event:
-	def _connect():
-		if _settings['modes']:
-			Command._mode(_ident['nickname'], '+' + _settings['modes'])
-		if _login['nickserv']:
-			Command._sendmsg('NickServ', 'IDENTIFY {0} {1}'.format(_ident['nickname'], _login['nickserv']))
-		if _login['operator']:
-			Bot._queue.append('OPER {0} {1}'.format(_ident['username'], _login['operator']))
-		Command._join(_settings['channel'], _settings['key'])
-
-	def _ctcp(nick, chan, msg):
-		pass
-
-	def _disconnect():
-		Bot._sock.close()
-		Bot._queue = list()
-		time.sleep(15)
-		Bot._connect()
-
-	def _invite(nick, chan):
-		pass
-
-	def _join(nick, chan):
-		pass
-
-	def _kick(nick, chan, kicked):
-		if kicked == _ident['nickname'] and chan == _settings['channel']:
-			time.sleep(3)
-			Command.join(chan, _Settings['key'])
-
-	def _message(nick, chan, msg):
-		if msg == '!test':
-			Command._sendmsg(chan, 'It Works!')
-
-	def _nick_in_use():
-		error_exit('The bot is already running or nick is in use!')
-
-	def _part(nick, chan):
-		pass
-
-	def _private(nick, msg):
-		pass
-
-	def _quit(nick):
-		pass
-
-	def _handle(data):
-		args = data.split()
-		if data.startswith('ERROR :Closing Link:'):
-			raise Exception('Connection has closed.')
-		elif data.startswith('ERROR :Reconnecting too fast, throttled.'):
-			raise Exception('Connection has closed. (throttled)')
-		elif args[0] == 'PING':
-			Command._raw('PONG ' + args[1][1:])
-		elif args[1] == '001':
-			Event._connect()
-		elif args[1] == '433':
-			Event._nick_in_use()
-		elif args[1] == 'INVITE':
-			nick = args[0].split('!')[0][1:]
-			chan = args[3][1:]
-			Event._invite(nick, chan)
-		elif args[1] == 'JOIN':
-			nick = args[0].split('!')[0][1:]
-			chan = args[2][1:]
-			Event._join(nick, chan)
-			Command._raw('WHOIS sniff')
-		elif args[1] == 'KICK':
-			nick   = args[0].split('!')[0][1:]
-			chan   = args[2]
-			kicked = args[3]
-			Event._kick(nick, chan, kicked)
-		elif args[1] == 'PART':
-			nick = args[0].split('!')[0][1:]
-			chan = args[2]
-			Event._part(nick, chan)
-		elif args[1] == 'PRIVMSG':
-			#ident = args[0][1:]
-			nick   = args[0].split('!')[0][1:]
-			chan   = args[2]
-			msg    = ' '.join(args[3:])[1:]
-			if msg.startswith('\001'):
-				Event._ctcp(nick, chan, msg)
-			elif chan == _ident['nickname']:
-				Event._private(nick, msg)
-			else:
-				Event._message(nick, chan, msg)
-		elif args[1] == 'QUIT':
-			nick = args[0].split('!')[0][1:]
-			Event._quit(nick)
-
-class Loop:
-	def _loops():
-		threading.Thread(target=Loop._queue).start()
-
-	def _queue():
-		while True:
+	def connect():
+		if config.connection.modes:
+			Command.raw(f'MODE {config.ident.nickname} +{config.connection.modes}')
+		if config.login.nickserv:
+			Command.sendmsg('NickServ', f'IDENTIFY {config.ident.nickname} {config.login.nickserv}')
+		if config.login.operator:
+			Command.raw(f'OPER {config.ident.username} {config.login.operator}')
+		Command.join_channel(config.connection.channel, config.connection.key)
+
+	async def disconnect():
+		Bot.writer.close()
+		await bot.writer.wait_closed()
+		asyncio.sleep(config.throttle.reconnect)
+
+	def nick_in_use():
+		new_nick = 'a' + str(random.randint(1000,9999))
+		Command.nick(new_nick)
+
+	async def handler():
+		while not Bot.reader.at_eof():
 			try:
-				if Bot._queue:
-					Command._raw(Bot._queue.pop(0))
-			except Exception as ex:
-				error('Error occured in the queue handler!', ex)
-			finally:
-				time.sleep(_settings['throttle'])
+				data = await Bot.reader.readline()
+				data = data.decode('utf-8').strip()
+				logging.info(data)
+				args = data.split()
+				if data.startswith('ERROR :Closing Link:'):
+					raise Exception('Connection has closed.')
+				elif data.startswith('ERROR :Reconnecting too fast, throttled.'):
+					raise Exception('Connection has closed. (throttled)')
+				elif args[0] == 'PING':
+					Command.raw('PONG ' + args[1][1:])
+				elif args[1] == '001': #RPL_WELCOME
+					Event.connect()
+				elif args[1] == '433': #ERR_NICKNAMEINUSE
+					Event.nick_in_use()
+				elif args[1] == 'KICK':
+					pass # handle kick
+			except (UnicodeDecodeError, UnicodeEncodeError):
+				pass
+			except:
+				logging.exception('Unknown error has occured!')
 
-# Main
-if _connection['proxy']:
-	try:
-		import socks
-	except ImportError:
-		error_exit('Missing PySocks module! (https://pypi.python.org/pypi/PySocks)')
-if _connection['ssl']:
-	import ssl
-else:
-	del _cert, _connection['ssl_verify']
-Bot = IRC()
-Bot._run()
-\ No newline at end of file
+##################################################
+
+class IrcBot:
+	def __init__(self):
+		self.options = {
+			'host'       : config.connection.server,
+			'port'       : config.connection.port,
+			'limit'      : 1024,
+			'ssl'        : ssl_ctx() if config.connection.ssl else None,
+			'family'     : socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET,
+			'local_addr' : (config.connection.vhost, 0) if config.connection.vhost else None
+		}
+		self.reader, self.writer = (None, None)
+
+	async def connect(self):
+		try:
+			self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**self.options), timeout=config.throttle.timeout)
+			if config.login.network:
+				Command.raw('PASS ' + config.login.network)
+			Command.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}')
+			Command.raw('NICK ' + config.ident.nickname)
+		except:
+			logging.exception('Failed to connect to IRC server!')
+		else:
+			await Event.handler()
+
+##################################################
+
+if __name__ == '__main__':
+	if not os.path.exists('logs'):
+		os.makedirs('logs')
+	sh = logging.StreamHandler()
+	sh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(message)s', '%I:%M %p'))
+	if config.settings.log:
+		fh = logging.handlers.RotatingFileHandler('logs/debug.log', maxBytes=250000, backupCount=7, encoding='utf-8')
+		fh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(filename)s.%(funcName)s.%(lineno)d | %(message)s', '%Y-%m-%d %I:%M %p'))
+		logging.basicConfig(level=logging.NOTSET, handlers=(sh,fh))
+		del fh,sh
+	else:
+		logging.basicConfig(level=logging.NOTSET, handlers=(sh,))
+		del sh
+
+	print('#'*56)
+	print('#{:^54}#'.format(''))
+	print('#{:^54}#'.format('Asyncronous IRC Bot Skeleton'))
+	print('#{:^54}#'.format('Developed by acidvegas in Python'))
+	print('#{:^54}#'.format('https://acid.vegas/skeleton'))
+	print('#{:^54}#'.format(''))
+	print('#'*56)
+
+	Bot = IrcBot()
+	asyncio.run(Bot.connect())
+\ No newline at end of file
diff --git a/skeleton/core/config.py b/skeleton/core/config.py
@@ -1,40 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# config.py
-
-class connection:
-	server     = 'irc.supernets.org'
-	port       = 6697
-	proxy      = None
-	ipv6       = False
-	ssl        = True
-	ssl_verify = False
-	vhost      = None
-	channel    = '#dev'
-	key        = None
-
-class cert:
-	key      = None
-	file     = None
-	password = None
-
-class ident:
-	nickname = 'DevBot'
-	username = 'devbot'
-	realname = 'acid.vegas/skeleton'
-
-class login:
-	network  = None
-	nickserv = None
-	operator = None
-
-class throttle:
-	command   = 3
-	reconnect = 10
-	rejoin    = 3
-
-class settings:
-	admin    = 'nick!user@host.name' # Must be in nick!user@host format (Can use wildcards here)
-	cmd_char = '!'
-	log      = False
-	modes    = None
diff --git a/skeleton/core/constants.py b/skeleton/core/constants.py
@@ -1,229 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# constants.py
-
-# Control Characters
-bold      = '\x02'
-color     = '\x03'
-italic    = '\x1D'
-underline = '\x1F'
-reverse   = '\x16'
-reset     = '\x0f'
-
-# Color Codes
-white       = '00'
-black       = '01'
-blue        = '02'
-green       = '03'
-red         = '04'
-brown       = '05'
-purple      = '06'
-orange      = '07'
-yellow      = '08'
-light_green = '09'
-cyan        = '10'
-light_cyan  = '11'
-light_blue  = '12'
-pink        = '13'
-grey        = '14'
-light_grey  = '15'
-
-# Events
-PASS     = 'PASS'
-NICK     = 'NICK'
-USER     = 'USER'
-OPER     = 'OPER'
-MODE     = 'MODE'
-SERVICE  = 'SERVICE'
-QUIT     = 'QUIT'
-SQUIT    = 'SQUIT'
-JOIN     = 'JOIN'
-PART     = 'PART'
-TOPIC    = 'TOPIC'
-NAMES    = 'NAMES'
-LIST     = 'LIST'
-INVITE   = 'INVITE'
-KICK     = 'KICK'
-PRIVMSG  = 'PRIVMSG'
-NOTICE   = 'NOTICE'
-MOTD     = 'MOTD'
-LUSERS   = 'LUSERS'
-VERSION  = 'VERSION'
-STATS    = 'STATS'
-LINKS    = 'LINKS'
-TIME     = 'TIME'
-CONNECT  = 'CONNECT'
-TRACE    = 'TRACE'
-ADMIN    = 'ADMIN'
-INFO     = 'INFO'
-SERVLIST = 'SERVLIST'
-SQUERY   = 'SQUERY'
-WHO      = 'WHO'
-WHOIS    = 'WHOIS'
-WHOWAS   = 'WHOWAS'
-KILL     = 'KILL'
-PING     = 'PING'
-PONG     = 'PONG'
-ERROR    = 'ERROR'
-AWAY     = 'AWAY'
-REHASH   = 'REHASH'
-DIE      = 'DIE'
-RESTART  = 'RESTART'
-SUMMON   = 'SUMMON'
-USERS    = 'USERS'
-WALLOPS  = 'WALLOPS'
-USERHOST = 'USERHOST'
-ISON     = 'ISON'
-
-# Event Numerics
-RPL_WELCOME          = '001'
-RPL_YOURHOST         = '002'
-RPL_CREATED          = '003'
-RPL_MYINFO           = '004'
-RPL_ISUPPORT         = '005'
-RPL_TRACELINK        = '200'
-RPL_TRACECONNECTING  = '201'
-RPL_TRACEHANDSHAKE   = '202'
-RPL_TRACEUNKNOWN     = '203'
-RPL_TRACEOPERATOR    = '204'
-RPL_TRACEUSER        = '205'
-RPL_TRACESERVER      = '206'
-RPL_TRACESERVICE     = '207'
-RPL_TRACENEWTYPE     = '208'
-RPL_TRACECLASS       = '209'
-RPL_STATSLINKINFO    = '211'
-RPL_STATSCOMMANDS    = '212'
-RPL_STATSCLINE       = '213'
-RPL_STATSILINE       = '215'
-RPL_STATSKLINE       = '216'
-RPL_STATSYLINE       = '218'
-RPL_ENDOFSTATS       = '219'
-RPL_UMODEIS          = '221'
-RPL_SERVLIST         = '234'
-RPL_SERVLISTEND      = '235'
-RPL_STATSLLINE       = '241'
-RPL_STATSUPTIME      = '242'
-RPL_STATSOLINE       = '243'
-RPL_STATSHLINE       = '244'
-RPL_LUSERCLIENT      = '251'
-RPL_LUSEROP          = '252'
-RPL_LUSERUNKNOWN     = '253'
-RPL_LUSERCHANNELS    = '254'
-RPL_LUSERME          = '255'
-RPL_ADMINME          = '256'
-RPL_ADMINLOC1        = '257'
-RPL_ADMINLOC2        = '258'
-RPL_ADMINEMAIL       = '259'
-RPL_TRACELOG         = '261'
-RPL_TRYAGAIN         = '263'
-RPL_NONE             = '300'
-RPL_AWAY             = '301'
-RPL_USERHOST         = '302'
-RPL_ISON             = '303'
-RPL_UNAWAY           = '305'
-RPL_NOWAWAY          = '306'
-RPL_WHOISUSER        = '311'
-RPL_WHOISSERVER      = '312'
-RPL_WHOISOPERATOR    = '313'
-RPL_WHOWASUSER       = '314'
-RPL_ENDOFWHO         = '315'
-RPL_WHOISIDLE        = '317'
-RPL_ENDOFWHOIS       = '318'
-RPL_WHOISCHANNELS    = '319'
-RPL_LIST             = '322'
-RPL_LISTEND          = '323'
-RPL_CHANNELMODEIS    = '324'
-RPL_NOTOPIC          = '331'
-RPL_TOPIC            = '332'
-RPL_INVITING         = '341'
-RPL_INVITELIST       = '346'
-RPL_ENDOFINVITELIST  = '347'
-RPL_EXCEPTLIST       = '348'
-RPL_ENDOFEXCEPTLIST  = '349'
-RPL_VERSION          = '351'
-RPL_WHOREPLY         = '352'
-RPL_NAMREPLY         = '353'
-RPL_LINKS            = '364'
-RPL_ENDOFLINKS       = '365'
-RPL_ENDOFNAMES       = '366'
-RPL_BANLIST          = '367'
-RPL_ENDOFBANLIST     = '368'
-RPL_ENDOFWHOWAS      = '369'
-RPL_INFO             = '371'
-RPL_MOTD             = '372'
-RPL_ENDOFINFO        = '374'
-RPL_MOTDSTART        = '375'
-RPL_ENDOFMOTD        = '376'
-RPL_YOUREOPER        = '381'
-RPL_REHASHING        = '382'
-RPL_YOURESERVICE     = '383'
-RPL_TIME             = '391'
-RPL_USERSSTART       = '392'
-RPL_USERS            = '393'
-RPL_ENDOFUSERS       = '394'
-RPL_NOUSERS          = '395'
-ERR_NOSUCHNICK       = '401'
-ERR_NOSUCHSERVER     = '402'
-ERR_NOSUCHCHANNEL    = '403'
-ERR_CANNOTSENDTOCHAN = '404'
-ERR_TOOMANYCHANNELS  = '405'
-ERR_WASNOSUCHNICK    = '406'
-ERR_TOOMANYTARGETS   = '407'
-ERR_NOSUCHSERVICE    = '408'
-ERR_NOORIGIN         = '409'
-ERR_NORECIPIENT      = '411'
-ERR_NOTEXTTOSEND     = '412'
-ERR_NOTOPLEVEL       = '413'
-ERR_WILDTOPLEVEL     = '414'
-ERR_BADMASK          = '415'
-ERR_UNKNOWNCOMMAND   = '421'
-ERR_NOMOTD           = '422'
-ERR_NOADMININFO      = '423'
-ERR_FILEERROR        = '424'
-ERR_NONICKNAMEGIVEN  = '431'
-ERR_ERRONEUSNICKNAME = '432'
-ERR_NICKNAMEINUSE    = '433'
-ERR_NICKCOLLISION    = '436'
-ERR_USERNOTINCHANNEL = '441'
-ERR_NOTONCHANNEL     = '442'
-ERR_USERONCHANNEL    = '443'
-ERR_NOLOGIN          = '444'
-ERR_SUMMONDISABLED   = '445'
-ERR_USERSDISABLED    = '446'
-ERR_NOTREGISTERED    = '451'
-ERR_NEEDMOREPARAMS   = '461'
-ERR_ALREADYREGISTRED = '462'
-ERR_NOPERMFORHOST    = '463'
-ERR_PASSWDMISMATCH   = '464'
-ERR_YOUREBANNEDCREEP = '465'
-ERR_KEYSET           = '467'
-ERR_CHANNELISFULL    = '471'
-ERR_UNKNOWNMODE      = '472'
-ERR_INVITEONLYCHAN   = '473'
-ERR_BANNEDFROMCHAN   = '474'
-ERR_BADCHANNELKEY    = '475'
-ERR_BADCHANMASK      = '476'
-ERR_BANLISTFULL      = '478'
-ERR_NOPRIVILEGES     = '481'
-ERR_CHANOPRIVSNEEDED = '482'
-ERR_CANTKILLSERVER   = '483'
-ERR_UNIQOPRIVSNEEDED = '485'
-ERR_NOOPERHOST       = '491'
-ERR_UMODEUNKNOWNFLAG = '501'
-ERR_USERSDONTMATCH   = '502'
-RPL_STARTTLS         = '670'
-ERR_STARTTLS         = '691'
-RPL_MONONLINE        = '730'
-RPL_MONOFFLINE       = '731'
-RPL_MONLIST          = '732'
-RPL_ENDOFMONLIST     = '733'
-ERR_MONLISTFULL      = '734'
-RPL_LOGGEDIN         = '900'
-RPL_LOGGEDOUT        = '901'
-ERR_NICKLOCKED       = '902'
-RPL_SASLSUCCESS      = '903'
-ERR_SASLFAIL         = '904'
-ERR_SASLTOOLONG      = '905'
-ERR_SASLABORTED      = '906'
-ERR_SASLALREADY      = '907'
-RPL_SASLMECHS        = '908'
diff --git a/skeleton/core/database.py b/skeleton/core/database.py
@@ -1,35 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# database.py
-
-import os
-import re
-import sqlite3
-
-# Globals
-db  = sqlite3.connect(os.path.join('data', 'bot.db'), check_same_thread=False)
-sql = db.cursor()
-
-def check():
-	tables = sql.execute('SELECT name FROM sqlite_master WHERE type=\'table\'').fetchall()
-	if not len(tables):
-		sql.execute('CREATE TABLE IGNORE (IDENT TEXT NOT NULL);')
-		db.commit()
-
-class Ignore:
-	def add(ident):
-		sql.execute('INSERT INTO IGNORE (IDENT) VALUES (?)', (ident,))
-		db.commit()
-
-	def check(ident):
-		for ignored_ident in Ignore.read():
-			if re.compile(ignored_ident.replace('*','.*')).search(ident):
-				return True
-		return False
-
-	def read():
-		return list(item[0] for item in sql.execute('SELECT IDENT FROM IGNORE ORDER BY IDENT ASC').fetchall())
-
-	def remove(ident):
-		sql.execute('DELETE FROM IGNORE WHERE IDENT=?', (ident,))
-		db.commit()
diff --git a/skeleton/core/debug.py b/skeleton/core/debug.py
@@ -1,81 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# debug.py
-
-import ctypes
-import logging
-import os
-import sys
-import time
-
-from logging.handlers import RotatingFileHandler
-
-import config
-
-def check_libs():
-	if config.connection.proxy:
-		try:
-			import socks
-		except ImportError:
-			error_exit('Missing PySocks module! (https://pypi.python.org/pypi/PySocks)')
-
-def check_privileges():
-	if check_windows():
-		if ctypes.windll.shell32.IsUserAnAdmin() != 0:
-			return True
-		else:
-			return False
-	else:
-		if os.getuid() == 0 or os.geteuid() == 0:
-			return True
-		else:
-			return False
-
-def check_version(major):
-	if sys.version_info.major == major:
-		return True
-	else:
-		return False
-
-def check_windows():
-	if os.name == 'nt':
-		return True
-	else:
-		return False
-
-def clear():
-	if check_windows():
-		os.system('cls')
-	else:
-		os.system('clear')
-
-def error(msg, reason=None):
-	if reason:
-		logging.debug(f'[!] - {msg} ({reason})')
-	else:
-		logging.debug('[!] - ' + msg)
-
-def error_exit(msg):
-	raise SystemExit('[!] - ' + msg)
-
-def info():
-	clear()
-	logging.debug('#'*56)
-	logging.debug('#{0}#'.format(''.center(54)))
-	logging.debug('#{0}#'.format('IRC Bot Skeleton'.center(54)))
-	logging.debug('#{0}#'.format('Developed by acidvegas in Python'.center(54)))
-	logging.debug('#{0}#'.format('https://git.acid.vegas/skeleton'.center(54)))
-	logging.debug('#{0}#'.format(''.center(54)))
-	logging.debug('#'*56)
-
-def irc(msg):
-	logging.debug('[~] - ' + msg)
-
-def setup_logger():
-	stream_handler = logging.StreamHandler(sys.stdout)
-	if config.settings.log:
-		log_file     = os.path.join(os.path.join('data','logs'), 'bot.log')
-		file_handler = RotatingFileHandler(log_file, maxBytes=256000, backupCount=3)
-		logging.basicConfig(level=logging.NOTSET, format='%(asctime)s | %(message)s', datefmt='%I:%M:%S', handlers=(file_handler,stream_handler))
-	else:
-		logging.basicConfig(level=logging.NOTSET, format='%(asctime)s | %(message)s', datefmt='%I:%M:%S', handlers=(stream_handler,))
diff --git a/skeleton/core/functions.py b/skeleton/core/functions.py
@@ -1,10 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# functions.py
-
-import re
-
-import config
-
-def is_admin(ident):
-	return re.compile(config.settings.admin.replace('*','.*')).search(ident)
diff --git a/skeleton/core/irc.py b/skeleton/core/irc.py
@@ -1,289 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# irc.py
-
-import socket
-import time
-
-import config
-import constants
-import database
-import debug
-import functions
-
-# Load optional modules
-if config.connection.ssl:
-	import ssl
-if config.connection.proxy:
-	try:
-		import sock
-	except ImportError:
-		debug.error_exit('Missing PySocks module! (https://pypi.python.org/pypi/PySocks)') # Required for proxy support.
-
-def color(msg, foreground, background=None):
-	if background:
-		return f'\x03{foreground},{background}{msg}{constants.reset}'
-	else:
-		return f'\x03{foreground}{msg}{constants.reset}'
-
-class IRC(object):
-	def __init__(self):
-		self.last   = 0
-		self.slow   = False
-		self.sock   = None
-		self.status = True
-
-	def connect(self):
-		try:
-			self.create_socket()
-			self.sock.connect((config.connection.server, config.connection.port))
-			self.register()
-		except socket.error as ex:
-			debug.error('Failed to connect to IRC server.', ex)
-			Events.disconnect()
-		else:
-			self.listen()
-
-	def create_socket(self):
-		family = socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET
-		if config.connection.proxy:
-			proxy_server, proxy_port = config.connection.proxy.split(':')
-			self.sock = socks.socksocket(family, socket.SOCK_STREAM)
-			self.sock.setblocking(0)
-			self.sock.settimeout(15)
-			self.sock.setproxy(socks.PROXY_TYPE_SOCKS5, proxy_server, int(proxy_port))
-		else:
-			self.sock = socket.socket(family, socket.SOCK_STREAM)
-		if config.connection.vhost:
-			self.sock.bind((config.connection.vhost, 0))
-		if config.connection.ssl:
-			ctx = ssl.SSLContext()
-			if config.cert.file:
-				ctx.load_cert_chain(config.cert.file, config.cert.key, config.cert.password)
-			if config.connection.ssl_verify:
-				ctx.verify_mode = ssl.CERT_REQUIRED
-				ctx.load_default_certs()
-			else:
-				ctx.check_hostname = False
-				ctx.verify_mode = ssl.CERT_NONE
-			self.sock = ctx.wrap_socket(self.sock)
-
-	def listen(self):
-		while True:
-			try:
-				data = self.sock.recv(2048).decode('utf-8')
-				for line in (line for line in data.split('\r\n') if line):
-					debug.irc(line)
-					if len(line.split()) >= 2:
-						Events.handle(line)
-			except (UnicodeDecodeError,UnicodeEncodeError):
-				pass
-			except Exception as ex:
-				debug.error('Unexpected error occured.', ex)
-				break
-		Events.disconnect()
-
-	def register(self):
-		if config.login.network:
-			Commands.raw('PASS ' + config.login.network)
-		Commands.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}')
-		Commands.nick(config.ident.nickname)
-
-
-
-class Commands:
-	def action(chan, msg):
-		Commands.sendmsg(chan, f'\x01ACTION {msg}\x01')
-
-	def ctcp(target, data):
-		Commands.sendmsg(target, f'\001{data}\001')
-
-	def error(target, data, reason=None):
-		if reason:
-			Commands.sendmsg(target, '[{0}] {1} {2}'.format(color('!', constants.red), data, color('({0})'.format(reason), constants.grey)))
-		else:
-			Commands.sendmsg(target, '[{0}] {1}'.format(color('!', constants.red), data))
-
-	def identify(nick, password):
-		Commands.sendmsg('nickserv', f'identify {nick} {password}')
-
-	def invite(nick, chan):
-		Commands.raw(f'INVITE {nick} {chan}')
-
-	def join_channel(chan, key=None):
-		Commands.raw(f'JOIN {chan} {key}') if key else Commands.raw('JOIN ' + chan)
-
-	def mode(target, mode):
-		Commands.raw(f'MODE {target} {mode}')
-
-	def nick(nick):
-		Commands.raw('NICK ' + nick)
-
-	def notice(target, msg):
-		Commands.raw(f'NOTICE {target} :{msg}')
-
-	def oper(user, password):
-		Commands.raw(f'OPER {user} {password}')
-
-	def part(chan, msg=None):
-		Commands.raw(f'PART {chan} {msg}') if msg else Commands.raw('PART ' + chan)
-
-	def quit(msg=None):
-		Commands.raw('QUIT :' + msg) if msg else Commands.raw('QUIT')
-
-	def raw(msg):
-		Bot.sock.send(bytes(msg + '\r\n', 'utf-8'))
-
-	def sendmsg(target, msg):
-		Commands.raw(f'PRIVMSG {target} :{msg}')
-
-	def topic(chan, text):
-		Commands.raw(f'TOPIC {chan} :{text}')
-
-
-
-class Events:
-	def connect():
-		if config.settings.modes:
-			Commands.mode(config.ident.nickname, '+' + config.settings.modes)
-		if config.login.nickserv:
-			Commands.identify(config.ident.nickname, config.login.nickserv)
-		if config.login.operator:
-			Commands.oper(config.ident.username, config.login.operator)
-		Commands.join_channel(config.connection.channel, config.connection.key)
-
-	def ctcp(nick, chan, msg):
-		pass
-
-	def disconnect():
-		Bot.sock.close()
-		time.sleep(config.throttle.reconnect)
-		Bot.connect()
-
-	def invite(nick, chan):
-		if nick == config.ident.nickname and chan == config.connection.channe:
-			Commands.join_channel(config.connection.channel, config.connection.key)
-
-	def join_channel(nick, chan):
-		pass
-
-	def kick(nick, chan, kicked):
-		if kicked == config.ident.nickname and chan == config.connection.channel:
-			time.sleep(config.throttle.rejoin)
-			Commands.join_channel(chan, config.connection.key)
-
-	def message(nick, ident, chan, msg):
-		try:
-			if chan == config.connection.channel and Bot.status:
-				if msg.startswith(config.settings.cmd_char):
-					if not database.Ignore.check(ident):
-						if time.time() - Bot.last < config.throttle.command and not functions.is_admin(ident):
-							if not Bot.slow:
-								Commands.sendmsg(chan, color('Slow down nerd!', constants.red))
-								Bot.slow = True
-						elif Bot.status or functions.is_admin(ident):
-							Bot.slow = False
-							args     = msg.split()
-							if msg == 'test':
-								while True:
-									Commands.raw('WHO')
-									time.sleep(0.5)
-							'''
-							if len(args) == 1:
-								if cmd == 'test':
-									Commands.sendmsg(chan, 'It works!')
-							elif len(args) >= 2:
-								if cmd == 'echo':
-									Commands.sendmsg(chan, args)
-							'''
-						Bot.last = time.time()
-		except Exception as ex:
-			Commands.error(chan, 'Command threw an exception.', ex)
-
-	def nick_in_use():
-		debug.error('The bot is already running or nick is in use.')
-
-	def part(nick, chan):
-		pass
-
-	def private(nick, ident, msg):
-		if functions.is_admin(ident):
-			args = msg.split()
-			if msg == '.ignore':
-				ignores = database.Ignore.read()
-				if ignores:
-					Commands.sendmsg(nick, '[{0}]'.format(color('Ignore List', constants.purple)))
-					for user in ignores:
-						Commands.sendmsg(nick, color(user, constants.yellow))
-					Commands.sendmsg(nick, '{0} {1}'.format(color('Total:', constants.light_blue), color(len(ignores), constants.grey)))
-				else:
-					Commands.error(nick, 'Ignore list is empty!')
-			elif msg == '.off':
-				Bot.status = False
-				Commands.sendmsg(nick, color('OFF', constants.red))
-			elif msg == '.on':
-				Bot.status = True
-				Commands.sendmsg(nick, color('ON', constants.green))
-			elif len(args) == 3:
-				if args[0] == '.ignore':
-					if args[1] == 'add':
-						user_ident = args[2]
-						if user_ident not in database.Ignore.hosts():
-							database.Ignore.add(nickname, user_ident)
-							Commands.sendmsg(nick, 'Ident {0} to the ignore list.'.format(color('added', constants.green)))
-						else:
-							Commands.error(nick, 'Ident is already on the ignore list.')
-					elif args[1] == 'del':
-						user_ident = args[2]
-						if user_ident in database.Ignore.hosts():
-							database.Ignore.remove(user_ident)
-							Commands.sendmsg(nick, 'Ident {0} from the ignore list.'.format(color('removed', constants.red)))
-						else:
-							Commands.error(nick, 'Ident does not exist in the ignore list.')
-
-	def quit(nick):
-		pass
-
-	def handle(data):
-		args = data.split()
-		if data.startswith('ERROR :Closing Link:'):
-			raise Exception('Connection has closed.')
-		elif args[0] == 'PING':
-			Commands.raw('PONG ' + args[1][1:])
-		elif args[1] == constants.RPL_WELCOME:
-			Events.connect()
-		elif args[1] == constants.ERR_NICKNAMEINUSE:
-			Events.nick_in_use()
-		elif args[1] == constants.INVITE and len(args) == 4:
-			nick = args[0].split('!')[0][1:]
-			chan = args[3][1:]
-			Events.invite(nick, chan)
-		elif args[1] == constants.JOIN and len(args) == 3:
-			nick = args[0].split('!')[0][1:]
-			chan = args[2][1:]
-			Events.join_channel(nick, chan)
-		elif args[1] == constants.KICK and len(args) >= 4:
-			nick   = args[0].split('!')[0][1:]
-			chan   = args[2]
-			kicked = args[3]
-			Events.kick(nick, chan, kicked)
-		elif args[1] == constants.PART and len(args) >= 3:
-			nick = args[0].split('!')[0][1:]
-			chan = args[2]
-			Events.part(nick, chan)
-		elif args[1] == constants.PRIVMSG and len(args) >= 4:
-			nick  = args[0].split('!')[0][1:]
-			ident = args[0].split('!')[1]
-			chan  = args[2]
-			msg   = data.split(f'{args[0]} PRIVMSG {chan} :')[1]
-			if msg.startswith('\001'):
-				Events.ctcp(nick, chan, msg)
-			elif chan == config.ident.nickname:
-				Events.private(nick, ident, msg)
-			else:
-				Events.message(nick, ident, chan, msg)
-		elif args[1] == constants.QUIT:
-			nick = args[0].split('!')[0][1:]
-			Events.quit(nick)
-
-Bot = IRC()
diff --git a/skeleton/data/cert/.gitignore b/skeleton/data/cert/.gitignore
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
-\ No newline at end of file
diff --git a/skeleton/data/logs/.gitignore b/skeleton/data/logs/.gitignore
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
-\ No newline at end of file
diff --git a/skeleton/modules/.gitignore b/skeleton/modules/.gitignore
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
-\ No newline at end of file
diff --git a/skeleton/skeleton.py b/skeleton/skeleton.py
@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
-# skeleton.py
-
-import os
-import sys
-
-sys.dont_write_bytecode = True
-os.chdir(sys.path[0] or '.')
-sys.path += ('core','modules')
-
-import debug
-
-debug.setup_logger()
-debug.info()
-if not debug.check_version(3):
-	debug.error_exit('Python 3 is required!')
-if debug.check_privileges():
-	debug.error_exit('Do not run as admin/root!')
-debug.check_libs()
-import database
-database.check()
-import irc
-irc.Bot.connect()