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

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 _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:
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
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:
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):
async def handler():
while not Bot.reader.at_eof():
try:
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':
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')
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':
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)
pass # handle kick
except (UnicodeDecodeError, UnicodeEncodeError):
pass
except:
logging.exception('Unknown error has occured!')
##################################################
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:
Event._message(nick, chan, msg)
elif args[1] == 'QUIT':
nick = args[0].split('!')[0][1:]
Event._quit(nick)
await Event.handler()
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'])
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
# 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()
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()