Removed Bot class in favor of a single instance Bot 'module'. Bot imports modules specified in config file. Fixed NICK change parsing. Protocol supports connect and disconnect callbacks.
This commit is contained in:
parent
508dc04eac
commit
cceb2515eb
191
bot.py
191
bot.py
|
@ -9,6 +9,8 @@ import logging
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
from protocol import IrcProtocol
|
from protocol import IrcProtocol
|
||||||
|
|
||||||
import modules
|
import modules
|
||||||
|
@ -19,6 +21,18 @@ try: _MOD_MAP
|
||||||
except NameError:
|
except NameError:
|
||||||
_MOD_MAP = {}
|
_MOD_MAP = {}
|
||||||
|
|
||||||
|
# single global bot object
|
||||||
|
try:
|
||||||
|
_BOT
|
||||||
|
|
||||||
|
# on a reload, reimport current module list from config
|
||||||
|
with open( _BOT['_cfg_file'] ) as cfg_file:
|
||||||
|
cfg = toml.loads( cfg_file.read() )
|
||||||
|
_load_modules( cfg['bot'].get( 'modules', [] ) )
|
||||||
|
|
||||||
|
except NameError:
|
||||||
|
_BOT = {}
|
||||||
|
|
||||||
def add_module( name ):
|
def add_module( name ):
|
||||||
""" adds a new named empty module definition to the global module map
|
""" adds a new named empty module definition to the global module map
|
||||||
'fixes' passed module name by removing top-level package name
|
'fixes' passed module name by removing top-level package name
|
||||||
|
@ -69,83 +83,119 @@ def parser():
|
||||||
|
|
||||||
return __deco__
|
return __deco__
|
||||||
|
|
||||||
class Bot:
|
# initialize the bot instance, takes a config filename
|
||||||
""" Bot class """
|
def init( loop, cfg_file ):
|
||||||
def __init__( self, loop, cfg ):
|
_BOT['loop'] = loop
|
||||||
self._loop = loop
|
_BOT['_cfg_file'] = cfg_file
|
||||||
self.cfg = cfg
|
# load config file
|
||||||
# list of network connections
|
cfg = toml.loads( open( cfg_file ).read() )
|
||||||
self._con = []
|
_BOT['cfg'] = cfg
|
||||||
# create and connect all servers in config
|
# import initial module list from config
|
||||||
for s in self.cfg['server']:
|
_load_modules( cfg['bot'].get( 'modules', [] ) )
|
||||||
ncon = IrcProtocol( self._loop, self.cfg['user'] )
|
# list of network connections
|
||||||
# add priv message callback to the connections
|
_BOT['cons'] = []
|
||||||
ncon.add_message_callback( self._on_priv_message )
|
# create and connect all servers in config
|
||||||
self._con.append( ncon )
|
for s in cfg.get( 'server', [] ):
|
||||||
self._loop.create_task( ncon.do_connect( s ) )
|
add_connection( s )
|
||||||
|
|
||||||
# called when a connected connection receives a priv message
|
# public getter functions for bot
|
||||||
# passes the connection that sent, destination of message,
|
def get_loop():
|
||||||
# the nick that sent, and the message
|
return _BOT.get( 'loop', None )
|
||||||
def _on_priv_message( self, con, dst, nick, msg ):
|
def get_cfg():
|
||||||
# check for command-like message
|
return _BOT.get( 'cfg', {} )
|
||||||
# message split into words, first word is command rest are parms list
|
def get_cons():
|
||||||
pfx = self.cfg['bot']['prefix']
|
return _BOT.get( 'cons', [] )
|
||||||
strp_msg = msg.lstrip()
|
|
||||||
if strp_msg[0 : len( pfx )] == pfx:
|
# add and connect to a new server
|
||||||
msg_words = strp_msg.split()
|
def add_connection( sv_cfg ):
|
||||||
cmd = msg_words[0][len( pfx ):].lower()
|
loop = get_loop()
|
||||||
parms = msg_words[1:]
|
ncon = IrcProtocol( loop, get_cfg().get( 'user' ) )
|
||||||
# check if command is in a registered module
|
# add connection callbacks
|
||||||
for k, m in _MOD_MAP.items():
|
ncon.add_message_callback( _on_priv_message )
|
||||||
if cmd in m['cmds'].keys():
|
ncon.add_connect_callback( _on_connect )
|
||||||
cmd_func = m['cmds'][cmd]
|
ncon.add_disconnect_callback( _on_disconnect )
|
||||||
con.log( '::CMD:: \'{}\' run by <{}> in ({}) with parms: {}'.format(
|
loop.create_task( ncon.do_connect( sv_cfg ) )
|
||||||
cmd, nick, dst, parms ) )
|
|
||||||
# if msg was sent as pm,
|
def _on_connect( con ):
|
||||||
# change dst to nick so say_to's can respond in pm
|
con.warning( 'ON CONNECT!' )
|
||||||
if dst == self.cfg['user']['nick']: dst = nick
|
cons = get_cons()
|
||||||
# check for admin
|
# add priv msg callback here since this could be a reconnect as well
|
||||||
if 'admin' in cmd_func.__dict__.keys() and nick != con.cfg['usr']['owner']:
|
# and it was removed on disconnect
|
||||||
con.say_to( dst, '> You do not have permission to execute this command.' )
|
#con.add_message_callback( _on_priv_message )
|
||||||
break
|
if con not in cons:
|
||||||
self._loop.create_task( cmd_func(
|
cons.append( con )
|
||||||
self, parms, {'con':con,'dst':dst,'nick':nick} ) )
|
|
||||||
|
def _on_disconnect( con ):
|
||||||
|
con.warning( 'ON DISCONNECT!' )
|
||||||
|
cons = get_cons()
|
||||||
|
if con in cons:
|
||||||
|
cons.remove( con )
|
||||||
|
|
||||||
|
# called when a connected connection receives a priv message
|
||||||
|
# passes the connection that sent, destination of message,
|
||||||
|
# the nick that sent, and the message
|
||||||
|
def _on_priv_message( con, dst, nick, msg ):
|
||||||
|
#con.log( '{} {} {} {}'.format( con, dst, nick, msg ) )
|
||||||
|
|
||||||
|
bcfg = get_cfg()
|
||||||
|
# check for command-like message
|
||||||
|
# message split into words, first word is command rest are parms list
|
||||||
|
pfx = bcfg['bot']['prefix']
|
||||||
|
strp_msg = msg.lstrip()
|
||||||
|
if strp_msg[0 : len( pfx )] == pfx:
|
||||||
|
msg_words = strp_msg.split()
|
||||||
|
cmd = msg_words[0][len( pfx ):].lower()
|
||||||
|
parms = msg_words[1:]
|
||||||
|
# check if command is in a registered module
|
||||||
|
for k, m in _MOD_MAP.items():
|
||||||
|
if cmd in m['cmds'].keys():
|
||||||
|
cmd_func = m['cmds'][cmd]
|
||||||
|
con.log( '::CMD:: \'{}\' run by <{}> in ({}) with parms: {}'.format(
|
||||||
|
cmd, nick, dst, parms ) )
|
||||||
|
# if msg was sent as pm,
|
||||||
|
# change dst to nick so say_to's can respond in pm
|
||||||
|
if dst == bcfg['user']['nick']: dst = nick
|
||||||
|
# check for admin flag
|
||||||
|
if 'admin' in cmd_func.__dict__.keys() and nick != con.cfg['usr']['owner']:
|
||||||
|
con.say_to( dst, '> You do not have permission to execute this command.' )
|
||||||
break
|
break
|
||||||
|
get_loop().create_task( cmd_func(
|
||||||
|
parms, {'con':con,'dst':dst,'nick':nick} ) )
|
||||||
|
break
|
||||||
|
|
||||||
# callback registered parsers, not for self messages though
|
# callback registered parsers, not for self messages though
|
||||||
if nick != self.cfg['user']['nick']:
|
if nick != bcfg['user']['nick']:
|
||||||
# call each registered parser
|
# call each registered parser
|
||||||
for v in _MOD_MAP.values():
|
for v in _MOD_MAP.values():
|
||||||
for p in v['parsers']:
|
for p in v['parsers']:
|
||||||
p( con, dst, nick, msg )
|
p( con, dst, nick, msg )
|
||||||
|
|
||||||
# core commands
|
# core commands
|
||||||
|
|
||||||
# join / part
|
# join / part
|
||||||
@command( 'join', True )
|
@command( 'join', True )
|
||||||
async def cmd_join( bot, parms, ctx ):
|
async def cmd_join( parms, ctx ):
|
||||||
""" Joins a channel. """
|
""" Joins a channel. """
|
||||||
|
|
||||||
con = ctx['con']
|
con = ctx['con']
|
||||||
if parms \
|
if parms \
|
||||||
and not (parms[0] in con.cfg['sv']['channels']) and parms[0][0] == '#':
|
and not (parms[0].lower() in con.cfg['sv']['channels']) and parms[0][0] == '#':
|
||||||
con.send( 'JOIN {}'.format( parms[0] ) )
|
con.send( 'JOIN {}'.format( parms[0] ) )
|
||||||
con.cfg['sv']['channels'].append( parms[0] )
|
con.cfg['sv']['channels'].append( parms[0].lower() )
|
||||||
|
|
||||||
@command( 'part', True )
|
@command( 'part', True )
|
||||||
async def cmd_part( bot, parms, ctx ):
|
async def cmd_part( parms, ctx ):
|
||||||
""" Leaves a channel. """
|
""" Leaves a channel. """
|
||||||
|
|
||||||
con = ctx['con']
|
con = ctx['con']
|
||||||
if parms \
|
if parms \
|
||||||
and parms[0] in con.cfg['sv']['channels']:
|
and parms[0].lower() in con.cfg['sv']['channels']:
|
||||||
con.send( 'PART {}'.format( parms[0] ) )
|
con.send( 'PART {}'.format( parms[0] ) )
|
||||||
con.cfg['sv']['channels'].remove( parms[0] )
|
con.cfg['sv']['channels'].remove( parms[0].lower() )
|
||||||
|
|
||||||
# gets the doc string for specified command
|
# gets the doc string for specified command
|
||||||
@command( 'help' )
|
@command( 'help' )
|
||||||
async def cmd_help( b, p, c ):
|
async def cmd_help( p, c ):
|
||||||
""" Shows help message for a specified command. """
|
""" Shows help message for a specified command. """
|
||||||
|
|
||||||
con = c['con']
|
con = c['con']
|
||||||
|
@ -168,7 +218,7 @@ async def cmd_help( b, p, c ):
|
||||||
|
|
||||||
# list commands
|
# list commands
|
||||||
@command( 'list' )
|
@command( 'list' )
|
||||||
async def cmd_list( b, p, c ):
|
async def cmd_list( p, c ):
|
||||||
""" Lists available commands. Specify a module name from 'modules' command to filter. """
|
""" Lists available commands. Specify a module name from 'modules' command to filter. """
|
||||||
|
|
||||||
cn = c['con']
|
cn = c['con']
|
||||||
|
@ -186,7 +236,7 @@ async def cmd_list( b, p, c ):
|
||||||
|
|
||||||
# list loaded modules
|
# list loaded modules
|
||||||
@command( 'modules' )
|
@command( 'modules' )
|
||||||
async def cmd_modules( b, p, c ):
|
async def cmd_modules( p, c ):
|
||||||
""" List all currently loaded modules. List commands within using 'list <module>' """
|
""" List all currently loaded modules. List commands within using 'list <module>' """
|
||||||
cn = c['con']
|
cn = c['con']
|
||||||
modlist = list(_MOD_MAP.keys())
|
modlist = list(_MOD_MAP.keys())
|
||||||
|
@ -194,14 +244,14 @@ async def cmd_modules( b, p, c ):
|
||||||
|
|
||||||
# list active connections
|
# list active connections
|
||||||
@command( 'connections', True )
|
@command( 'connections', True )
|
||||||
async def cmd_connections( b, p, c ):
|
async def cmd_connections( p, c ):
|
||||||
""" List currently connected connections """
|
""" List currently connected connections """
|
||||||
conlist = [c.cfg['sv']['name'] for c in b._con]
|
conlist = [c.cfg['sv']['name'] for c in get_cons()]
|
||||||
c['con'].say_to( c['dst'], '> {}'.format( conlist ) )
|
c['con'].say_to( c['dst'], '> {}'.format( conlist ) )
|
||||||
|
|
||||||
# reload a specified module
|
# reload a specified module
|
||||||
@command( 'reload', True )
|
@command( 'reload', True )
|
||||||
async def cmd_import( bot, p, ctx ):
|
async def cmd_import( p, ctx ):
|
||||||
""" Reloads a previously loaded module. """
|
""" Reloads a previously loaded module. """
|
||||||
con = ctx['con']
|
con = ctx['con']
|
||||||
dst = ctx['dst']
|
dst = ctx['dst']
|
||||||
|
@ -221,7 +271,7 @@ async def cmd_import( bot, p, ctx ):
|
||||||
|
|
||||||
# connect to a new server
|
# connect to a new server
|
||||||
@command( 'connect', True )
|
@command( 'connect', True )
|
||||||
async def cmd_connect( b, p, c ):
|
async def cmd_connect( p, c ):
|
||||||
"""
|
"""
|
||||||
Connects to a new network.
|
Connects to a new network.
|
||||||
Parameters: 'name' 'host'
|
Parameters: 'name' 'host'
|
||||||
|
@ -231,25 +281,21 @@ async def cmd_connect( b, p, c ):
|
||||||
if not p and len( p ) < 2: return
|
if not p and len( p ) < 2: return
|
||||||
|
|
||||||
new_sv_cfg = {'host':p[1],'port':6697,'name':p[0]}
|
new_sv_cfg = {'host':p[1],'port':6697,'name':p[0]}
|
||||||
ncon = IrcProtocol( b._loop, con.cfg['usr'] )
|
add_connection( new_sv_cfg )
|
||||||
# add message callback
|
|
||||||
ncon.add_message_callback( b._on_priv_message )
|
|
||||||
b._con.append( ncon )
|
|
||||||
b._loop.create_task( ncon.do_connect( new_sv_cfg ) )
|
|
||||||
con.say_to( c['dst'], 'OMW...' )
|
con.say_to( c['dst'], 'OMW...' )
|
||||||
|
|
||||||
# disconnect from currently connected server
|
# disconnect from currently connected server
|
||||||
@command( 'disconnect', True )
|
@command( 'disconnect', True )
|
||||||
async def cmd_discon( b, p, c ):
|
async def cmd_discon( p, c ):
|
||||||
""" Disconnects from currently connected network """
|
""" Disconnects from currently connected network """
|
||||||
con = c['con']
|
con = c['con']
|
||||||
con._stop = True
|
con._stop = True
|
||||||
con.send( 'QUIT :POOF' )
|
con.send( 'QUIT :POOF' )
|
||||||
b._con.remove( con )
|
#b._con.remove( con )
|
||||||
|
|
||||||
# list of names on chan
|
# list of names on chan
|
||||||
@command( 'names', True )
|
@command( 'names', True )
|
||||||
async def cmd_names( b, p, c ):
|
async def cmd_names( p, c ):
|
||||||
cn = c['con']
|
cn = c['con']
|
||||||
nmstr = ''
|
nmstr = ''
|
||||||
for k,v in cn.names.items():
|
for k,v in cn.names.items():
|
||||||
|
@ -258,9 +304,10 @@ async def cmd_names( b, p, c ):
|
||||||
|
|
||||||
cn.say_to( c['dst'], '> {}'.format( nmstr ) )
|
cn.say_to( c['dst'], '> {}'.format( nmstr ) )
|
||||||
|
|
||||||
cn.log( str(cn.names) )
|
#cn.log( str(cn.names) )
|
||||||
|
|
||||||
# custom modules
|
# imports a list of module names from the modules package
|
||||||
importlib.import_module( '.misc', 'modules' )
|
def _load_modules( mod_list ):
|
||||||
importlib.import_module( '.qdb', 'modules' )
|
for n in mod_list:
|
||||||
|
importlib.import_module( '.{}'.format( n ), 'modules' )
|
||||||
|
|
||||||
|
|
5
cfg.toml
5
cfg.toml
|
@ -21,8 +21,9 @@ name = "Me Mow"
|
||||||
# command prefix
|
# command prefix
|
||||||
prefix = ";"
|
prefix = ";"
|
||||||
|
|
||||||
# TBD??
|
# list of module names to import from modules package
|
||||||
modules = []
|
# additions are imported on bot reload
|
||||||
|
modules = ['misc','qdb']
|
||||||
|
|
||||||
##
|
##
|
||||||
## networks/servers config
|
## networks/servers config
|
||||||
|
|
11
main.py
11
main.py
|
@ -8,9 +8,7 @@ import logging
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import toml
|
import bot
|
||||||
|
|
||||||
from bot import Bot
|
|
||||||
|
|
||||||
# only use uvloop if not using pypy
|
# only use uvloop if not using pypy
|
||||||
if py_imp() != 'PyPy':
|
if py_imp() != 'PyPy':
|
||||||
|
@ -21,13 +19,12 @@ if __name__ == '__main__':
|
||||||
evloop = asyncio.get_event_loop()
|
evloop = asyncio.get_event_loop()
|
||||||
evloop.set_default_executor( concurrent.futures.ThreadPoolExecutor( 4 ) )
|
evloop.set_default_executor( concurrent.futures.ThreadPoolExecutor( 4 ) )
|
||||||
|
|
||||||
# parse bot configuration
|
# determine config filename
|
||||||
cfg_file = './cfg.toml'
|
cfg_file = './cfg.toml'
|
||||||
if len( sys.argv ) > 1: cfg_file = sys.argv[1]
|
if len( sys.argv ) > 1: cfg_file = sys.argv[1]
|
||||||
cfg = toml.loads( open( cfg_file ).read() )
|
|
||||||
|
|
||||||
# create a bot instance
|
# initialize bot instance, takes name of a config file
|
||||||
bot = Bot( evloop, cfg )
|
bot.init( evloop, cfg_file )
|
||||||
|
|
||||||
try: evloop.run_forever()
|
try: evloop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -18,12 +18,12 @@ PY_APP_URL = 'https://tumbolia-sopel.appspot.com/py/{}'
|
||||||
#req_ses = requests.Session()
|
#req_ses = requests.Session()
|
||||||
|
|
||||||
@bot.command( 'eval' )
|
@bot.command( 'eval' )
|
||||||
async def cmd_eval( b, p, c ):
|
async def cmd_eval( p, c ):
|
||||||
""" Evaluates Python code. Trust me, it's safe. """
|
""" Evaluates Python code. Trust me, it's safe. """
|
||||||
evstr = urllib.parse.quote( ' '.join( p ) )
|
evstr = urllib.parse.quote( ' '.join( p ) )
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = await b._loop.run_in_executor( None,
|
res = await bot.get_loop().run_in_executor( None,
|
||||||
functools.partial( requests.get, PY_APP_URL.format( evstr ), timeout=3.0 ) )
|
functools.partial( requests.get, PY_APP_URL.format( evstr ), timeout=3.0 ) )
|
||||||
|
|
||||||
rstr = res.text.rstrip().lstrip()
|
rstr = res.text.rstrip().lstrip()
|
||||||
|
@ -42,14 +42,15 @@ async def cmd_eval( b, p, c ):
|
||||||
# parser test
|
# parser test
|
||||||
@bot.parser()
|
@bot.parser()
|
||||||
def parse_misc( c, d, n, m ):
|
def parse_misc( c, d, n, m ):
|
||||||
if m.split()[0].lower() == '!botlist' or m.split()[0].lower() == '!rollcall':
|
if len(m.split()) > 0:
|
||||||
pfx = ';' # HAX
|
if m.split()[0].lower() == '!botlist' or m.split()[0].lower() == '!rollcall':
|
||||||
c.say_to( d, ('> dustbot | Owner: {} | '
|
pfx = bot.get_cfg()['bot'].get( 'prefix' )
|
||||||
'Source: https://tildegit.org/slipyx/dustbot | Prefix: \'{}\'. | '
|
c.say_to( d, ('> dustbot | Owner: {} | '
|
||||||
'Commands: See \'{}list\'.').format( c.cfg['usr']['owner'],pfx,pfx ) )
|
'Source: https://tildegit.org/slipyx/dustbot | Prefix: \'{}\'. | '
|
||||||
|
'Commands: See \'{}list\'.').format( c.cfg['usr']['owner'],pfx,pfx ) )
|
||||||
|
|
||||||
@bot.command( 'rfk' )
|
@bot.command( 'rfk' )
|
||||||
async def cmd_rfk( b, p, c ):
|
async def cmd_rfk( p, c ):
|
||||||
""" Try to find kitten! """
|
""" Try to find kitten! """
|
||||||
nki = ''
|
nki = ''
|
||||||
if random.randint(1,20) == random.randint(1,20): nki = 'YOU FOUND KITTEN! CONGRATULATIONS!'
|
if random.randint(1,20) == random.randint(1,20): nki = 'YOU FOUND KITTEN! CONGRATULATIONS!'
|
||||||
|
|
|
@ -7,7 +7,7 @@ import random
|
||||||
import requests
|
import requests
|
||||||
import bs4
|
import bs4
|
||||||
|
|
||||||
from bot import command
|
import bot
|
||||||
|
|
||||||
# map of supported databases
|
# map of supported databases
|
||||||
QDB_URLS = {
|
QDB_URLS = {
|
||||||
|
@ -18,8 +18,8 @@ QDB_URLS = {
|
||||||
'tilde': 'https://quotes.tilde.chat/random'
|
'tilde': 'https://quotes.tilde.chat/random'
|
||||||
}
|
}
|
||||||
|
|
||||||
@command( 'qdb' )
|
@bot.command( 'qdb' )
|
||||||
async def get_quote( bot, parms, ctx ):
|
async def get_quote( parms, ctx ):
|
||||||
"""
|
"""
|
||||||
Grabs a random quote from specified quote database,
|
Grabs a random quote from specified quote database,
|
||||||
default random or database with same name as current network.
|
default random or database with same name as current network.
|
||||||
|
|
36
protocol.py
36
protocol.py
|
@ -43,7 +43,8 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
self._trans = None # protocol's transport
|
self._trans = None # protocol's transport
|
||||||
self._stop = False # True if doing a manual stop and dont try to reconnect
|
self._stop = False # True if doing a manual stop and dont try to reconnect
|
||||||
self.cfg = {'usr':user_cfg,'sv':{}} # server config set on first connect
|
self.cfg = {'usr':user_cfg,'sv':{}} # server config set on first connect
|
||||||
self._msg_cb = [] # list of priv message callbacks
|
# callbacks
|
||||||
|
self._cb = {'message':[],'connect':[],'disconnect':[]}
|
||||||
self.names = {} # map of names on server to list of channels they're visible in
|
self.names = {} # map of names on server to list of channels they're visible in
|
||||||
|
|
||||||
def connection_made( self, transport ):
|
def connection_made( self, transport ):
|
||||||
|
@ -51,12 +52,18 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
self._trans = transport
|
self._trans = transport
|
||||||
self.send( 'NICK {}'.format( self.cfg['usr']['nick'] ) )
|
self.send( 'NICK {}'.format( self.cfg['usr']['nick'] ) )
|
||||||
self.send( 'USER {} 0 * :{}'.format( self.cfg['usr']['user'], self.cfg['usr']['name'] ) )
|
self.send( 'USER {} 0 * :{}'.format( self.cfg['usr']['user'], self.cfg['usr']['name'] ) )
|
||||||
|
for c in self._cb['connect']: c( self )
|
||||||
|
|
||||||
def connection_lost( self, exc ):
|
def connection_lost( self, exc ):
|
||||||
self._trans.close()
|
self._trans.close()
|
||||||
self.names.clear() # clear names
|
self.names.clear() # clear names
|
||||||
if exc is None: exc = 'EOF'
|
if exc is None: exc = 'EOF'
|
||||||
self.warning( 'Connection lost to server \'{}\'! ({})'.format( self.cfg['sv']['host'], exc ) )
|
self.warning( 'Connection lost to server \'{}\'! ({})'.format( self.cfg['sv']['host'], exc ) )
|
||||||
|
# callbacks
|
||||||
|
for c in self._cb['disconnect']: c( self )
|
||||||
|
# remove all callbacks
|
||||||
|
#for cb in self._cb.values():
|
||||||
|
#cb.clear()
|
||||||
# if was a manual stop, dont attempt reconnect
|
# if was a manual stop, dont attempt reconnect
|
||||||
if self._stop:
|
if self._stop:
|
||||||
self.on_stop()
|
self.on_stop()
|
||||||
|
@ -86,7 +93,7 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
raw_user = words[0]
|
raw_user = words[0]
|
||||||
nick = raw_user[1 : words[0].find( '!' )]
|
nick = raw_user[1 : words[0].find( '!' )]
|
||||||
msg = raw_msg[raw_msg.find( ':' , 1 ) + 1 :]
|
msg = raw_msg[raw_msg.find( ':' , 1 ) + 1 :]
|
||||||
for cb in self._msg_cb: cb( self, dst, nick, msg )
|
for cb in self._cb['message']: cb( self, dst, nick, msg )
|
||||||
# on names
|
# on names
|
||||||
elif words[1] == '353':
|
elif words[1] == '353':
|
||||||
#self.log( '::NAMES:: {}'.format( raw_msg.decode() ) )
|
#self.log( '::NAMES:: {}'.format( raw_msg.decode() ) )
|
||||||
|
@ -114,9 +121,12 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
self.log( '::QUIT:: {} has quit the server.'.format( nick ) )
|
self.log( '::QUIT:: {} has quit the server.'.format( nick ) )
|
||||||
self.quit_name( nick )
|
self.quit_name( nick )
|
||||||
elif 'NICK' in words[1]:
|
elif 'NICK' in words[1]:
|
||||||
|
self.log( raw_msg )
|
||||||
nick = words[0][1:words[0].find('!')]
|
nick = words[0][1:words[0].find('!')]
|
||||||
#self.log( '::NICK:: {} is now known as {}.'.format( nick, words[2][1:] ) )
|
#self.log( '::NICK:: {} is now known as {}.'.format( nick, words[2][1:] ) )
|
||||||
self.change_name( nick, words[2][1:] )
|
newnick = words[2]
|
||||||
|
if newnick[0] == ':': newnick = newnick[1:]
|
||||||
|
self.change_name( nick, newnick )
|
||||||
elif 'KICK' in words[1]:
|
elif 'KICK' in words[1]:
|
||||||
if words[3] == self.cfg['usr']['nick']:
|
if words[3] == self.cfg['usr']['nick']:
|
||||||
self.log( '::KICK:: {} has been kicked from {}.'.format( words[3], words[2] ) )
|
self.log( '::KICK:: {} has been kicked from {}.'.format( words[3], words[2] ) )
|
||||||
|
@ -135,10 +145,10 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
|
|
||||||
def part_name( self, name, chan ):
|
def part_name( self, name, chan ):
|
||||||
""" Removes chan from name """
|
""" Removes chan from name """
|
||||||
self.names[name].remove( chan )
|
if chan in self.names[name]: self.names[name].remove( chan )
|
||||||
# if chans are empty, remove name completely
|
# if chans are empty, remove name completely
|
||||||
if not self.names[name]: self.quit_name( name )
|
if not self.names[name]: self.quit_name( name )
|
||||||
# when self leaves a chan, remove chan from all existing names
|
# when bot leaves a chan, remove chan from all existing names
|
||||||
if name == self.cfg['usr']['nick']:
|
if name == self.cfg['usr']['nick']:
|
||||||
nms = []
|
nms = []
|
||||||
for k,v in self.names.items():
|
for k,v in self.names.items():
|
||||||
|
@ -150,11 +160,14 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
|
|
||||||
def quit_name( self, name ):
|
def quit_name( self, name ):
|
||||||
""" Removes name from connection """
|
""" Removes name from connection """
|
||||||
self.names.pop( name )
|
self.names.pop( name, [] )
|
||||||
|
|
||||||
def change_name( self, name, new ):
|
def change_name( self, name, new ):
|
||||||
""" Renames an existing name """
|
""" Renames an existing name """
|
||||||
self.names[new] = self.names.pop( name )
|
#if new in self.names:
|
||||||
|
self.names[new] = self.names.pop( name, [] )
|
||||||
|
#else: self.names[new] = []
|
||||||
|
#self.log( 'NICK {} changed to {} w/now: {}'.format( name, new, self.names[new] ) )
|
||||||
|
|
||||||
# callbacks
|
# callbacks
|
||||||
def add_message_callback( self, cb ):
|
def add_message_callback( self, cb ):
|
||||||
|
@ -162,7 +175,14 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
Adds a function to callback when a priv message is received.
|
Adds a function to callback when a priv message is received.
|
||||||
Passes in self, dst, nick, and the message
|
Passes in self, dst, nick, and the message
|
||||||
"""
|
"""
|
||||||
self._msg_cb.append( cb )
|
if cb not in self._cb['message']:
|
||||||
|
self._cb['message'].append( cb )
|
||||||
|
def add_connect_callback( self, cb ):
|
||||||
|
if cb not in self._cb['connect']:
|
||||||
|
self._cb['connect'].append( cb )
|
||||||
|
def add_disconnect_callback( self, cb ):
|
||||||
|
if cb not in self._cb['disconnect']:
|
||||||
|
self._cb['disconnect'].append( cb )
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
def log( self, msg, level=logging.INFO ):
|
def log( self, msg, level=logging.INFO ):
|
||||||
|
|
Loading…
Reference in New Issue