Updated to be asyncronous and cleaned up source majorly

This commit is contained in:
Dionysus 2021-05-03 18:49:03 -04:00
parent 825b549153
commit 35d810d89b
Signed by: acidvegas
GPG Key ID: EF4B922DB85DC9DE
18 changed files with 400 additions and 975 deletions

View File

@ -1,6 +1,6 @@
ISC License 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 Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above

View File

@ -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)* * [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)* * [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/ - http://www.irchelp.org/protocol/rfc/
###### Mirrors ## Mirrors
- [acid.vegas](https://acid.vegas/skeleton) *(main)* - [acid.vegas](https://acid.vegas/skeleton) *(main)*
- [SuperNETs](https://git.supernets.org/acidvegas/skeleton)
- [GitHub](https://github.com/acidvegas/skeleton) - [GitHub](https://github.com/acidvegas/skeleton)
- [GitLab](https://gitlab.com/acidvegas/skeleton) - [GitLab](https://gitlab.com/acidvegas/skeleton)

54
advanced/core/bot.py Normal file
View File

@ -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()

43
advanced/core/commands.py Normal file
View File

@ -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}')

39
advanced/core/config.py Normal file
View File

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

61
advanced/core/events.py Normal file
View File

@ -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()

40
advanced/skeleton.py Normal file
View File

@ -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())

View File

@ -1,276 +1,177 @@
#!/usr/bin/env python #!/usr/bin/env python
# IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton) # Asyncronoua IRC Bot Skeleton - Developed by acidvegas in Python (https://acid.vegas/skeleton)
# skeleton.py
import socket import asyncio
import logging
import logging.handlers
import os
import random
import time 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 class config:
bold = '\x02' class connection:
italic = '\x1D' server = 'irc.supernets.org'
underline = '\x1F' port = 6697
reverse = '\x16' ipv6 = False
reset = '\x0f' ssl = True
white = '00' ssl_verify = False
black = '01' vhost = None
blue = '02' channel = '#dev'
green = '03' key = None
red = '04' modes = None
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): class cert:
if background: file = None
return f'\x03{foreground},{background}{msg}{reset}' password = None
else:
return f'\x03{foreground}{msg}{reset}'
def debug(msg): class ident:
print(f'{get_time()} | [~] - {msg}') nickname = 'skeleton'
username = 'skeleton'
realname = 'acid.vegas/skeleton'
def error(msg, reason=None): class login:
if reason: network = None
print(f'{get_time()} | [!] - {msg} ({reason})') nickserv = None
else: operator = None
print(f'{get_time()} | [!] - {msg}')
def error_exit(msg): class settings:
raise SystemExit(f'{get_time()} | [!] - {msg}') admin = 'nick!user@host' # Must be in nick!user@host format (Wildcards accepted)
log = False
def get_time(): class throttle:
return time.strftime('%I:%M:%S') command = 3
message = 0.5
reconnect = 15
rejoin = 5
timeout = 15
class IRC(object): ##################################################
def __init__(self):
self._queue = list()
self._sock = None
def _run(self): def ssl_ctx():
Loop._loops() import ssl
self._connect() ctx = ssl.create_default_context()
if not config.connection.ssl_verify:
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.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE ctx.verify_mode = ssl.CERT_NONE
self._sock = ctx.wrap_socket(self._sock) if config.cert.file:
ctx.load_cert_chain(config.cert.file, password=config.cert.password)
return ctx
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: class Command:
def _action(target, msg): def join_channel(chan, key=None):
Bot._queue.append(chan, f'\x01ACTION {msg}\x01') Command.raw(f'JOIN {chan} {key}') if key else Command.raw('JOIN ' + chan)
def _ctcp(target, data): def mode(target, mode):
Bot._queue.append(target, f'\001{data}\001') Command.raw(f'MODE {target} {mode}')
def _invite(nick, chan): def nick(new_nick):
Bot._queue.append(f'INVITE {nick} {chan}') Command.raw('NICK ' + new_nick)
def _join(chan, key=None): def raw(data):
Bot._queue.append(f'JOIN {chan} {key}') if key else Bot._queue.append('JOIN ' + chan) Bot.writer.write(data[:510].encode('utf-8') + b'\r\n')
def _mode(target, mode): def sendmsg(target, msg):
Bot._queue.append(f'MODE {target} {mode}') Command.raw(f'PRIVMSG {target} :{msg}')
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: class Event:
def _connect(): def connect():
if _settings['modes']: if config.connection.modes:
Command._mode(_ident['nickname'], '+' + _settings['modes']) Command.raw(f'MODE {config.ident.nickname} +{config.connection.modes}')
if _login['nickserv']: if config.login.nickserv:
Command._sendmsg('NickServ', 'IDENTIFY {0} {1}'.format(_ident['nickname'], _login['nickserv'])) Command.sendmsg('NickServ', f'IDENTIFY {config.ident.nickname} {config.login.nickserv}')
if _login['operator']: if config.login.operator:
Bot._queue.append('OPER {0} {1}'.format(_ident['username'], _login['operator'])) Command.raw(f'OPER {config.ident.username} {config.login.operator}')
Command._join(_settings['channel'], _settings['key']) Command.join_channel(config.connection.channel, config.connection.key)
def _ctcp(nick, chan, msg): async def disconnect():
pass Bot.writer.close()
await bot.writer.wait_closed()
asyncio.sleep(config.throttle.reconnect)
def _disconnect(): def nick_in_use():
Bot._sock.close() new_nick = 'a' + str(random.randint(1000,9999))
Bot._queue = list() Command.nick(new_nick)
time.sleep(15)
Bot._connect()
def _invite(nick, chan): async def handler():
pass while not Bot.reader.at_eof():
try:
def _join(nick, chan): data = await Bot.reader.readline()
pass data = data.decode('utf-8').strip()
logging.info(data)
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() args = data.split()
if data.startswith('ERROR :Closing Link:'): if data.startswith('ERROR :Closing Link:'):
raise Exception('Connection has closed.') raise Exception('Connection has closed.')
elif data.startswith('ERROR :Reconnecting too fast, throttled.'): elif data.startswith('ERROR :Reconnecting too fast, throttled.'):
raise Exception('Connection has closed. (throttled)') raise Exception('Connection has closed. (throttled)')
elif args[0] == 'PING': elif args[0] == 'PING':
Command._raw('PONG ' + args[1][1:]) Command.raw('PONG ' + args[1][1:])
elif args[1] == '001': elif args[1] == '001': #RPL_WELCOME
Event._connect() Event.connect()
elif args[1] == '433': elif args[1] == '433': #ERR_NICKNAMEINUSE
Event._nick_in_use() 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': elif args[1] == 'KICK':
nick = args[0].split('!')[0][1:] pass # handle kick
chan = args[2] except (UnicodeDecodeError, UnicodeEncodeError):
kicked = args[3] pass
Event._kick(nick, chan, kicked) except:
elif args[1] == 'PART': logging.exception('Unknown error has occured!')
nick = args[0].split('!')[0][1:]
chan = args[2] ##################################################
Event._part(nick, chan)
elif args[1] == 'PRIVMSG': class IrcBot:
#ident = args[0][1:] def __init__(self):
nick = args[0].split('!')[0][1:] self.options = {
chan = args[2] 'host' : config.connection.server,
msg = ' '.join(args[3:])[1:] 'port' : config.connection.port,
if msg.startswith('\001'): 'limit' : 1024,
Event._ctcp(nick, chan, msg) 'ssl' : ssl_ctx() if config.connection.ssl else None,
elif chan == _ident['nickname']: 'family' : socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET,
Event._private(nick, msg) '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: else:
Event._message(nick, chan, msg) await Event.handler()
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(): if __name__ == '__main__':
while True: if not os.path.exists('logs'):
try: os.makedirs('logs')
if Bot._queue: sh = logging.StreamHandler()
Command._raw(Bot._queue.pop(0)) sh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(message)s', '%I:%M %p'))
except Exception as ex: if config.settings.log:
error('Error occured in the queue handler!', ex) fh = logging.handlers.RotatingFileHandler('logs/debug.log', maxBytes=250000, backupCount=7, encoding='utf-8')
finally: fh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(filename)s.%(funcName)s.%(lineno)d | %(message)s', '%Y-%m-%d %I:%M %p'))
time.sleep(_settings['throttle']) logging.basicConfig(level=logging.NOTSET, handlers=(sh,fh))
del fh,sh
else:
logging.basicConfig(level=logging.NOTSET, handlers=(sh,))
del sh
# Main print('#'*56)
if _connection['proxy']: print('#{:^54}#'.format(''))
try: print('#{:^54}#'.format('Asyncronous IRC Bot Skeleton'))
import socks print('#{:^54}#'.format('Developed by acidvegas in Python'))
except ImportError: print('#{:^54}#'.format('https://acid.vegas/skeleton'))
error_exit('Missing PySocks module! (https://pypi.python.org/pypi/PySocks)') print('#{:^54}#'.format(''))
if _connection['ssl']: print('#'*56)
import ssl
else: Bot = IrcBot()
del _cert, _connection['ssl_verify'] asyncio.run(Bot.connect())
Bot = IRC()
Bot._run()

View File

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

View File

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

View File

@ -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()

View File

@ -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,))

View File

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

View File

@ -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()

View File

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

View File

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

View File

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

View File

@ -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()