🢀︎ dickserv :: 11ceb7f


commit 11ceb7fba6a96a8e6e308107858fd82eac0941d3
Author: acidvegas <acid.vegas@acid.vegas>
Date:   Mon Jun 24 22:13:57 2019 -0400

    Initial commit

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..69997e8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2019, 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
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..06b5053
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+###### Requirments
+* [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.)*
+* [beautifulsoup4](https://pypi.python.org/pypi/beautifulsoup4)
+* [google-api-python-client](https://pypi.python.org/pypi/google-api-python-client)
+
+###### Commands
+| Command | Description |
+| --- | --- |
+| @dickserv | Information about the bot. |
+| @dickserv help | Information about the commands. |
+| coin \<cryptocurrency> | Get the USD value for \<cryptocurrency>. |
+| date | Get the current date and time. |
+| define \<word> | Get the definition of \<word>. |
+| drug \<query> | Lookup information on \<drug> on tripsit. |
+| g \<query> | Search Google for \<query>. |
+| imdb \<query/ttid> [year] | Search \<query/ttid> on IMDb. |
+| isup \<url> | Check if \<url> is up or not. |
+| netsplit \<query> | Search for \<query> on NetSplit. |
+| r \<subreddit> | Read top posts from \<subreddit> |
+| talent | RIP DITTLE DIP DIP DIP DIP IT\'S YA BIRTHDAY!!1@11! |
+| todo | Read your todo list. |
+| todo add \<data> | Add \<data> to your todo list. |
+| todo del \<num>| | Delete the \<num> todo result. |
+| tpb \<query> | Searc \<query on ThePirateBay. |
+| ud \<word> | Get the urban dictionary definition of \<word>. |
+| uptime | Get the amount of time DickServ has been running. |
+| w \<zip_code> | Get the weather for \<zip>. |
+| wolfram \<ask> | Get the results of \<query> from WolframAlpha. |
+| yt \<query> | Search \<query> on YouTube. |
+
+###### Admin Commands (Private Message)
+| Command | Description |
+| --- | --- |
+| config | View the config settings. |
+| config \<setting> \<value> | Change \<setting> to \<value>. |
+| ignore | View the ignore list. |
+| ignore add \<ident> | Add a user to the ignore list. |
+| ignore del \<ident> | Remove a user from the ignore list. |
+| ignore reset | Remove all ignores. |
+| off | Toggle the usage of the bot commands. |
+| on | Toggle the usage of the bot commands. |
+| todo | List all todos. |
+| todo expire | Remove all expired todos. |
+| todo reset | Remove all todos. |
+
+###### Mirrors
+- [acid.vegas](https://acid.vegas/dickserv) *(main)*
+- [SuperNETs](https://git.supernets.org/acidvegas/dickserv)
+- [GitHub](https://github.com/acidvegas/dickserv)
+- [GitLab](https://gitlab.com/acidvegas/dickserv)
diff --git a/dickserv/core/commands.py b/dickserv/core/commands.py
new file mode 100644
index 0000000..6903dfe
--- /dev/null
+++ b/dickserv/core/commands.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# commands.py
+
+import cryptocurrency
+import dictionary
+import google
+import imdb
+import isup
+import netsplit
+import reddit
+import tpb
+import tripsit
+import weather
+import wolfram
+import youtube
\ No newline at end of file
diff --git a/dickserv/core/config.py b/dickserv/core/config.py
new file mode 100644
index 0000000..cd663c2
--- /dev/null
+++ b/dickserv/core/config.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# config.py
+
+class connection:
+	server     = 'irc.server.com'
+	port       = 6697
+	ipv6       = False
+	ssl        = True
+	ssl_verify = False
+	proxy      = None
+	vhost      = None
+	channel    = '#chats'
+	key        = None
+
+class cert:
+	key      = None
+	file     = None
+	password = None
+
+class ident:
+	nickname = 'DickServ'
+	username = 'dickserv'
+	realname = 'acid.vegas/dickserv'
+
+class login:
+	network  = None
+	nickserv = None
+	operator = None
+
+class settings:
+	admin    = 'user@host.name'
+	cmd_char = '.'
+	log      = False
+	modes    = None
+
+class api:
+	google_api_key       = 'CHANGEME' # https://console.developers.google.com/
+	google_cse_id        = 'CHANGEME' # https://cse.google.com/
+	omdbapi_key          = 'CHANGEME' # http://www.omdbapi.com/apikey.aspx
+	wolfram_api_key      = 'CHANGEME' # http://products.wolframalpha.com/api/
+	wunderground_api_key = 'CHANGEME' # https://www.wunderground.com/weather/api/
diff --git a/dickserv/core/constants.py b/dickserv/core/constants.py
new file mode 100644
index 0000000..2f1a041
--- /dev/null
+++ b/dickserv/core/constants.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# 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'
\ No newline at end of file
diff --git a/dickserv/core/database.py b/dickserv/core/database.py
new file mode 100644
index 0000000..87e4593
--- /dev/null
+++ b/dickserv/core/database.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# database.py
+
+import os
+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);')
+		sql.execute('CREATE TABLE SETTINGS (SETTING TEXT NOT NULL, VALUE INTEGER NOT NULL);')
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?, ?)', ('max_results',  5))
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?, ?)', ('max_todo',   100))
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?, ?)', ('max_todo_per', 5))
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?, ?)', ('todo_expire',  7))
+		sql.execute('CREATE TABLE TODO (DATE TEXT NOT NULL, IDENT TEXT NOT NULL, DATA TEXT NOT NULL);')
+		db.commit()
+
+class Ignore:
+	def add(ident):
+		sql.execute('INSERT INTO IGNORE (IDENT) VALUES (?)', (ident,))
+		db.commit()
+
+	def idents():
+		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()
+
+	def reset():
+		sql.execute('DROP TABLE IGNORE')
+		sql.execute('CREATE TABLE IGNORE (IDENT TEXT NOT NULL);')
+		db.commit()
+
+class Settings:
+	def get(setting):
+		return sql.execute('SELECT VALUE FROM SETTINGS WHERE SETTING=?', (setting,)).fetchone()[0]
+
+	def read():
+		return sql.execute('SELECT SETTING,VALUE FROM SETTINGS ORDER BY SETTING ASC').fetchall()
+
+	def settings():
+		return list(item[0] for item in sql.execute('SELECT SETTING FROM SETTINGS').fetchall())
+
+	def update(setting, value):
+		sql.execute('UPDATE SETTINGS SET VALUE=? WHERE SETTING=?', (value, setting))
+		db.commit()
+
+class Todo:
+	def add(date, ident, data):
+		sql.execute('INSERT INTO TODO (DATE,IDENT,DATA) VALUES (?,?,?)', (date, ident, data))
+		db.commit()
+
+	def expire_check():
+		todos = set(list(item[0] for item in sql.execute('SELECT DATE FROM TODO').fetchall()))
+		for date in todos:
+			if functions.timespan(date) > Settings.get('todo_expire'):
+				sql.execute('DELETE FROM TODO WHERE DATE=?', (date,))
+				db.commit()
+
+	def idents():
+		return list(item[0] for item in sql.execute('SELECT IDENT FROM TODO').fetchall())
+
+	def read(ident=None):
+		if ident:
+			return list(item[0] for item in sql.execute('SELECT DATA FROM TODO WHERE IDENT=?', (ident,)).fetchall())
+		else:
+			return sql.execute('SELECT DATE,IDENT,DATA FROM TODO ORDER BY DATE ASC, IDENT ASC, DATA ASC').fetchall()
+
+	def remove(ident, data):
+		sql.execute('DELETE FROM TODO WHERE IDENT=? AND DATA=?', (ident, data))
+		db.commit()
+
+	def reset():
+		sql.execute('DROP TABLE TODO')
+		sql.execute('CREATE TABLE TODO (DATE TEXT NOT NULL, IDENT TEXT NOT NULL, DATA TEXT NOT NULL);')
+		db.commit()
\ No newline at end of file
diff --git a/dickserv/core/debug.py b/dickserv/core/debug.py
new file mode 100644
index 0000000..a178193
--- /dev/null
+++ b/dickserv/core/debug.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# 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 \'socks\' module! (https://pypi.python.org/pypi/PySocks)')
+	try:
+		import bs4
+	except ImportError:
+		error_exit('Missing \'bs4\' module. (https://pypi.python.org/pypi/beautifulsoup4)')
+	try:
+		import googleapiclient.discovery
+	except ImportError:
+		error_exit('Missing \'google-api-python-client\' module. (https://pypi.python.org/pypi/google-api-python-client/)')
+
+
+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('DickServ IRC'.center(54)))
+	logging.debug('#{0}#'.format('Developed by acidvegas in Python'.center(54)))
+	logging.debug('#{0}#'.format('https://acid.vegas/dickserv'.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,))
\ No newline at end of file
diff --git a/dickserv/core/functions.py b/dickserv/core/functions.py
new file mode 100644
index 0000000..c041c03
--- /dev/null
+++ b/dickserv/core/functions.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# functions.py
+
+import datetime
+import random
+import re
+import time
+
+def between(source, start, stop):
+	data = re.compile(start + '(.*?)' + stop, re.IGNORECASE|re.MULTILINE).search(source)
+	if data:
+		return data.group(1)
+	else:
+		return False
+
+def current_date():
+	return time.strftime('%A, %B %d, %Y - %I:%M %p')
+
+def floatint(data):
+    if data.isdigit():
+        return int(data)
+    else:
+        return float(data)
+
+def get_date():
+	return datetime.date.today().strftime('%m/%d/%Y')
+
+def get_datetime(data):
+	return datetime.datetime.strptime(data, '%m/%d/%Y')
+
+def luck(odds):
+	if random_int(1,odds) == 1:
+		return True
+	else:
+		return False
+
+def random_int(min, max):
+	return random.randint(min, max)
+
+def timespan(date):
+	delta = datetime.date(get_date()) - datetime.date(get_datetime(date))
+	return delta.days
+
+def trim(data, max_length):
+	if len(data) > max_length:
+		return data[:max_length] + '...'
+	else:
+		return data
+
+def uptime(start_time):
+	uptime = datetime.datetime(1,1,1) + datetime.timedelta(seconds=time.time() - start_time)
+	return f'{uptime.day-1} Days, {uptime.hour} Hours, {uptime.minute} Minutes, {uptime.second} Seconds'
\ No newline at end of file
diff --git a/dickserv/core/httplib.py b/dickserv/core/httplib.py
new file mode 100644
index 0000000..4c27ad1
--- /dev/null
+++ b/dickserv/core/httplib.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# httplib.py
+
+import json
+import os
+import re
+import urllib.parse
+import urllib.request
+
+from bs4 import BeautifulSoup
+
+def clean_url(url):
+    for prefix in ('https://', 'http://', 'www.'):
+        if url.startswith(prefix):
+            url = url[len(prefix):]
+    if url[-1:] == '/':
+        url = url[:-1]
+    return url
+
+def data_quote(data):
+    return urllib.parse.quote(data)
+
+def data_encode(data):
+    return urllib.parse.urlencode(data)
+
+def get_file(url):
+    return os.path.basename(url)
+
+def get_json(url):
+    return json.loads(get_source(url))
+
+def get_size(url):
+    content_length = int(get_url(url).getheader('content-length'))
+    for unit in ('B','KB','MB','GB','TB','PB','EB','ZB'):
+        if abs(content_length) < 1024.0:
+            return '{0:.2f}'.format(content_length) + unit
+        content_length /= 1024.0
+    return '{0:.2f}'.format(content_length) + 'YB'
+
+def get_source(url):
+    source  = get_url(url)
+    charset = source.headers.get_content_charset()
+    if charset:
+        return source.read().decode(charset)
+    else:
+        return source.read().decode()
+
+def get_title(url):
+    source = get_source(url)
+    soup   = BeautifulSoup(source, 'html.parser')
+    return ' '.join(soup.title.string.split())
+
+def get_type(url):
+    return get_url(url).info().get_content_type()
+
+def get_url(url):
+    req = urllib.request.Request(url)
+    req.add_header('User-Agent', 'DickServ/1.0')
+    return urllib.request.urlopen(req, timeout=10)
+
+def parse_urls(data):
+    return re.compile('(?:http[s]?:\/\/|www.)(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', re.IGNORECASE).findall(data)
diff --git a/dickserv/core/irc.py b/dickserv/core/irc.py
new file mode 100644
index 0000000..c7d4bb3
--- /dev/null
+++ b/dickserv/core/irc.py
@@ -0,0 +1,479 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv)
+# irc.py
+
+import socket
+import time
+
+import config
+import constants
+import database
+import debug
+import functions
+import httplib
+
+from commands import *
+
+# Load optional modules
+if config.connection.ssl:
+	import ssl
+if config.connection.proxy:
+	import sock
+
+def color(msg, foreground, background=None):
+	if foreground == 'random':
+		foreground = '{0:0>2}'.format(functions.random_int(2,13))
+	if background == 'random':
+		background = '{0:0>2}'.format(functions.random_int(2,13))
+	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.start  = 0
+		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(1024).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 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 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 raw(msg):
+		msg = msg.replace('\r','').replace('\n','')[:450]
+		DickServ.sock.send(bytes(msg + '\r\n', 'utf-8'))
+
+	def sendmsg(target, msg):
+		Commands.raw(f'PRIVMSG {target} :{msg}')
+
+
+
+class Events:
+	def connect():
+		DickServ.start = time.time()
+		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 disconnect():
+		DickServ.sock.close()
+		time.sleep(10)
+		DickServ.connect()
+
+	def kick(nick, chan, kicked):
+		if kicked == config.ident.nickname and chan == config.connection.channel:
+			time.sleep(3)
+			Commands.join_channel(chan, config.connection.key)
+
+	def message(nick, ident, chan, msg):
+		try:
+			if chan == config.connection.channel and (DickServ.status or ident == config.settings.admin):
+				if not msg.startswith(config.settings.cmd_char):
+					urls = httplib.parse_urls(msg)
+					if urls:
+						if time.time() - DickServ.last > 3:
+							DickServ.last = time.time()
+							Events.url(chan, urls[0])
+					elif msg == '@dickserv':
+						Commands.sendmsg(chan, constants.bold + 'DickServ IRC Bot - Developed by acidvegas in Python - https://acid.vegas/dickserv')
+					elif msg == '@dickserv help':
+						Commands.sendmsg(chan, 'https://git.supernets.org/acidvegas/dickserv#commands')
+					elif msg == 'h' and functions.luck(4):
+						Commands.sendmsg(chan, 'h')
+					elif 'qhat' in msg:
+						Commands.sendmsg(chan, 'Q:)')
+				elif ident not in database.Ignore.idents():
+					if time.time() - DickServ.last < 3 and ident != config.settings.admin:
+						if not DickServ.slow:
+							Commands.sendmsg(chan, color('Slow down nerd!', constants.red))
+							DickServ.slow = True
+					else:
+						DickServ.slow = False
+						args = msg.split()
+						argz = msg[len(args[0])+1:]
+						cmd  = args[0][1:]
+						if len(args) == 1:
+							if cmd == 'date':
+								Commands.sendmsg(chan, functions.current_date())
+							elif cmd == 'talent':
+								if functions.luck(1000):
+									Commands.sendmsg(chan, color(f' !!! HOLY FUCKING SHIT {nick} ACHIEVED TALENT !!! ',               'random', 'random'))
+									Commands.sendmsg(chan, color(' !!! RIP DITTLE DIP DIP DIP DIP IT\'S YOUR BIRTHDAY !!! ',          'random', 'random'))
+									Commands.sendmsg(chan, color(f' !!! CAN WE HAVE A GOT DAMN MOMENT OF SILENCE FOR {nick} :) !!! ', 'random', 'random'))
+									Commands.sendmsg(chan, color(' !!! GOT DAMN XD THIS IS TOO CRAZY LIKE...DAMN HAHA. DAMN. !!! ',   'random', 'random'))
+								else:
+									Commands.sendmsg(chan, color('(^)', 'random'))
+							elif cmd == 'todo':
+								todos = database.Todo.read(ident)
+								if todos:
+									for item in todos:
+										Commands.notice(nick, '[{0}] {1}'.format(color(todos.index(item)+1, constants.pink), item))
+								else:
+									Commands.notice(nick, 'You have no saved todos.')
+							elif cmd == 'uptime':
+								Commands.sendmsg(chan, functions.uptime(DickServ.start))
+						elif len(args) == 2:
+							if cmd == 'coin':
+								api = cryptocurrency.get(args[1])
+								if api:
+									Commands.sendmsg(chan, '{0} {1} - ${2:,.2f}'.format(color(api['name'], constants.white), color('({0})'.format(api['symbol']), constants.grey), float(api['price_usd'])))
+								else:
+									Commands.error(chan, 'Invalid cryptocurrency name!')
+							elif cmd == 'drug':
+								api = tripsit.drug(args[1])
+								if api:
+									Commands.sendmsg(chan, '{0} - {1}'.format(color(api['name'], constants.yellow), api['desc']))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'define':
+								definition = dictionary.define(args[1])
+								if definition:
+									Commands.sendmsg(chan, '{0} - {1}: {2}'.format(color('Definition', constants.white, constants.blue), args[1].lower(), definition))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'isup':
+								Commands.sendmsg(chan, '{0} is {1}'.format(args[1], isup.check(args[1])))
+							elif cmd == 'r':
+								api = reddit.read(args[1])
+								if api:
+									data = list(api.keys())
+									for i in data:
+										count = str(data.index(i)+1)
+										Commands.sendmsg(chan, '[{0}] {1} [{2}|{3}/{4}|{5}]'.format(color(count, constants.pink), functions.trim(i, 100), color(str(api[i]['score']), constants.white), color('+' + str(api[i]['ups']), constants.green), color('-' + str(api[i]['downs']), constants.red), color(api[i]['comments'], constants.white)))
+										Commands.sendmsg(chan, ' - ' + color(api[i]['url'], constants.grey))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'w':
+								if args[1].isdigit():
+									api = weather.lookup(args[1])
+									if api:
+										Commands.sendmsg(chan, api)
+									else:
+										Commands.error(chan, 'No results found.')
+								else:
+									Commands.error(chan, 'Invalid arguments.')
+						if len(args) >= 2:
+							if cmd == 'g':
+								api = google.search(argz, database.Settings.get('max_results'))
+								if api:
+									for result in api:
+										count = api.index(result)+1
+										Commands.sendmsg(chan, '[{0}] {1}'.format(color(count, constants.pink), result['title']))
+										Commands.sendmsg(chan, ' - ' + color(result['link'], constants.grey))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'imdb':
+								api = imdb.search(argz)
+								if api:
+									Commands.sendmsg(chan, '{0} {1} {2} {3}'.format(color('Title  :', constants.white), api['Title'], api['Year'], color(api['Rated'], constants.grey)))
+									Commands.sendmsg(chan, '{0} {1}{2}'.format(color('Link   :', constants.white), constants.underline, color('https://imdb.com/title/' +  api['imdbID'], constants.light_blue)))
+									Commands.sendmsg(chan, '{0} {1}'.format(color('Genre  :', constants.white), api['Genre']))
+									if api['imdbRating'] == 'N/A':
+										Commands.sendmsg(chan, '{0} {1} 0/10'.format(color('Rating :', constants.white), color('★★★★★★★★★★', constants.grey)))
+									else:
+										Commands.sendmsg(chan, '{0} {1}{2} {3}/10'.format(color('Rating :', constants.white), color('★'*round(float(api['imdbRating'])), constants.yellow), color('★'*(10-round(float(api['imdbRating']))), constants.grey), api['imdbRating']))
+									Commands.sendmsg(chan, '{0} {1}'.format(color('Plot   :', constants.white), api['Plot']))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'netsplit':
+								api = netsplit.search(argz)
+								if api:
+									data = list(api.keys())
+									for i in data:
+										count = str(data.index(i)+1)
+										Commands.sendmsg(chan, '[{0}] {1} {2} / {3}'.format(color(count, constants.pink), color(i, constants.light_blue), color('({0})'.format(api[i]['users']), constants.grey), color(api[i]['network'], constants.red)))
+										Commands.sendmsg(chan, color(' - ' + api[i]['topic'], constants.grey))
+								else:
+									Commands.error(chan, 'No results found.')
+							elif cmd == 'todo' and len(args) >= 3:
+								if len(args) >= 3 and args[1] == 'add':
+									todos = database.Todo.read(ident)
+									if len(todos) <= database.Settings.get('max_todo_per') and len(database.Todo.read()) <= database.Settings.get('max_todo'):
+										argz = argz[4:]
+										if argz not in todos:
+											database.Todo.add(functions.get_date(), ident, argz)
+											Commands.notice(nick, 'Todo added to database!')
+										else:
+											Commands.notice(nick, 'Todo already in database!')
+									else:
+										Commands.notice(nick, 'Maximum todos reached!')
+								elif len(args) == 3 and args[1] == 'del':
+									num = args[2]
+									if num.isdigit():
+										num   = int(num)
+										todos = database.Todo.read(ident)
+										if todos:
+											if num <= len(todos):
+												for item in todos:
+													count = todos.index(item)+1
+													if count == num:
+														database.Todo.remove(ident, item)
+														break
+												Commands.notice(nick, 'Todo removed from database!')