Initial commit

This commit is contained in:
Dionysus 2019-06-28 02:23:40 -04:00
commit 4a96baa378
No known key found for this signature in database
GPG Key ID: 05114B46832BDBDB
13 changed files with 1016 additions and 0 deletions

15
LICENSE Normal file
View File

@ -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.

12
README.md Normal file
View File

@ -0,0 +1,12 @@
###### 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
- http://www.irchelp.org/protocol/rfc/
###### 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)

275
skeleton.py Normal file
View File

@ -0,0 +1,275 @@
#!/usr/bin/env python
# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
import socket
import time
import threading
# Configuration
_connection = {'server':'irc.server.com', '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()
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 _mode(target, mode):
Bot._queue.append(f'MODE {target} {mode}')
def _nick(nick):
Bot._queue.append('NICK ' + nick)
def _notice(target, msg):
Bot._queue.append(f'NOTICE {target} :{msg}')
def _part(chan, msg=None):
Bot._queue.append(f'PART {chan} {msg}') if msg else Bot._queue.append('PART ' + chan)
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(_setting['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':
Bot._queue.append(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)
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:
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'])
# 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['verify']
Bot = IRC()
Bot._run()

40
skeleton/core/config.py Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
# config.py
class connection:
server = 'irc.server.com'
port = 6667
proxy = None
ipv6 = False
ssl = False
ssl_verify = False
vhost = None
channel = '#dev'
key = None
class cert:
key = None
file = None
password = None
class ident:
nickname = 'skeleton'
username = 'skeleton'
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

229
skeleton/core/constants.py Normal file
View File

@ -0,0 +1,229 @@
#!/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'

35
skeleton/core/database.py Normal file
View File

@ -0,0 +1,35 @@
#!/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()

81
skeleton/core/debug.py Normal file
View File

@ -0,0 +1,81 @@
#!/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,))

View File

@ -0,0 +1,10 @@
#!/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)

283
skeleton/core/irc.py Normal file
View File

@ -0,0 +1,283 @@
#!/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 msg 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 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()

4
skeleton/data/cert/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

4
skeleton/data/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

4
skeleton/modules/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

24
skeleton/skeleton.py Normal file
View File

@ -0,0 +1,24 @@
#!/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()