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
Copyright (c) 2019, acidvegas <acid.vegas@acid.vegas>
Copyright (c) 2021, acidvegas <acid.vegas@acid.vegas>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@ -12,4 +12,4 @@ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

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)*
* [PySocks](https://pypi.python.org/pypi/PySocks) *(**Optional:** For using the `proxy` setting)*
###### IRC RCF Reference
## Information
The repository comes with 2 skeletons. A simple, single-file skeleton for basic bots & an advanced structured skeleton for more complex bots.
This is just a basic structure to help setup a bot. The bots have no use by default. It is asyncronous, can log to file, handle basic I/O, flood control, etc.
## IRC RCF Reference
- http://www.irchelp.org/protocol/rfc/
###### Mirrors
## Mirrors
- [acid.vegas](https://acid.vegas/skeleton) *(main)*
- [SuperNETs](https://git.supernets.org/acidvegas/skeleton)
- [GitHub](https://github.com/acidvegas/skeleton)
- [GitLab](https://gitlab.com/acidvegas/skeleton)

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

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