mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-25 04:02:45 +01:00
relay: basic JOIN handling
This commit is contained in:
parent
45cef19eaa
commit
6b9ec694b7
202
plugins/relay.py
202
plugins/relay.py
@ -7,17 +7,19 @@ import sched
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import string
|
import string
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
dbname = "pylinkrelay.db"
|
dbname = "pylinkrelay.db"
|
||||||
|
relayusers = defaultdict(dict)
|
||||||
|
queued_users = []
|
||||||
|
|
||||||
def normalizeNick(irc, nick, separator="/"):
|
def normalizeNick(irc, netname, nick, separator="/"):
|
||||||
orig_nick = nick
|
orig_nick = nick
|
||||||
protoname = irc.proto.__name__
|
protoname = irc.proto.__name__
|
||||||
maxnicklen = irc.maxnicklen
|
maxnicklen = irc.maxnicklen
|
||||||
netname = irc.name
|
|
||||||
if protoname == 'charybdis':
|
if protoname == 'charybdis':
|
||||||
# Charybdis doesn't allow / in usernames, and will quit with
|
# Charybdis doesn't allow / in usernames, and will quit with
|
||||||
# a protocol violation if there is one.
|
# a protocol violation if there is one.
|
||||||
@ -45,7 +47,7 @@ def normalizeNick(irc, nick, separator="/"):
|
|||||||
# but couldn't be created due to a nick conflict.
|
# but couldn't be created due to a nick conflict.
|
||||||
# This can happen when someone steals a relay user's nick.
|
# This can happen when someone steals a relay user's nick.
|
||||||
new_sep = separator + separator[-1]
|
new_sep = separator + separator[-1]
|
||||||
nick = normalizeNick(irc, orig_nick, separator=new_sep)
|
nick = normalizeNick(irc, netname, orig_nick, separator=new_sep)
|
||||||
finalLength = len(nick)
|
finalLength = len(nick)
|
||||||
assert finalLength <= maxnicklen, "Normalized nick %r went over max " \
|
assert finalLength <= maxnicklen, "Normalized nick %r went over max " \
|
||||||
"nick length (got: %s, allowed: %s!" % (nick, finalLength, maxnicklen)
|
"nick length (got: %s, allowed: %s!" % (nick, finalLength, maxnicklen)
|
||||||
@ -68,13 +70,137 @@ def exportDB(scheduler):
|
|||||||
with open(dbname, 'wb') as f:
|
with open(dbname, 'wb') as f:
|
||||||
pickle.dump(db, f, protocol=4)
|
pickle.dump(db, f, protocol=4)
|
||||||
|
|
||||||
def initializechannel(irc, channel):
|
def findRelay(chanpair):
|
||||||
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
|
if chanpair in db: # This chanpair is a shared channel; others link to it
|
||||||
|
return chanpair
|
||||||
|
# This chanpair is linked *to* a remote channel
|
||||||
|
for name, dbentry in db.items():
|
||||||
|
if chanpair in dbentry['links']:
|
||||||
|
return name
|
||||||
|
|
||||||
def removechannel(irc, channel):
|
def initializeChannel(homeirc, channel):
|
||||||
|
homeirc.proto.joinClient(homeirc, homeirc.pseudoclient.uid, channel)
|
||||||
|
|
||||||
|
def handle_join(irc, numeric, command, args):
|
||||||
|
channel = args['channel']
|
||||||
|
if not findRelay((irc.name, channel)):
|
||||||
|
# No relay here, return.
|
||||||
|
return
|
||||||
|
modes = args['modes']
|
||||||
|
ts = args['ts']
|
||||||
|
users = set(args['users'])
|
||||||
|
users.update(irc.channels[channel].users)
|
||||||
|
for user in users:
|
||||||
|
try:
|
||||||
|
if irc.users[user].remote:
|
||||||
|
# Is the .remote atrribute set? If so, don't relay already
|
||||||
|
# relayed clients; that'll trigger an endless loop!
|
||||||
|
continue
|
||||||
|
except AttributeError: # Nope, it isn't.
|
||||||
|
pass
|
||||||
|
if user == irc.pseudoclient.uid:
|
||||||
|
# We don't need to clone the PyLink pseudoclient... That's
|
||||||
|
# meaningless.
|
||||||
|
continue
|
||||||
|
userobj = irc.users[user]
|
||||||
|
userpair_index = relayusers.get((irc.name, user))
|
||||||
|
ident = userobj.ident
|
||||||
|
host = userobj.host
|
||||||
|
realname = userobj.realname
|
||||||
|
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
||||||
|
for name, remoteirc in utils.networkobjects.items():
|
||||||
|
nick = normalizeNick(remoteirc, irc.name, userobj.nick)
|
||||||
|
if name == irc.name:
|
||||||
|
# Don't relay things to their source network...
|
||||||
|
continue
|
||||||
|
# If the user (stored here as {(netname, UID):
|
||||||
|
# {network1: UID1, network2: UID2}}) exists, don't spawn it
|
||||||
|
# again!
|
||||||
|
u = None
|
||||||
|
if userpair_index is not None:
|
||||||
|
u = userpair_index.get(irc.name)
|
||||||
|
if u is None: # .get() returns None if not found
|
||||||
|
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
||||||
|
host=host, realname=realname).uid
|
||||||
|
remoteirc.users[u].remote = irc.name
|
||||||
|
log.debug('(%s) Spawning client %s (UID=%s)', irc.name, nick, u)
|
||||||
|
relayusers[(irc.name, userobj.uid)][remoteirc.name] = u
|
||||||
|
remoteirc.users[u].remote = irc.name
|
||||||
|
remoteirc.proto.joinClient(remoteirc, u, channel)
|
||||||
|
'''
|
||||||
|
chanpair = findRelay((homeirc.name, channel))
|
||||||
|
all_links = [chanpair] + list(db[chanpair]['links'])
|
||||||
|
# Iterate through all the (network, channel) pairs related
|
||||||
|
# to the channel.
|
||||||
|
log.debug('all_links: %s', all_links)
|
||||||
|
for link in all_links:
|
||||||
|
network, channel = link
|
||||||
|
if network == homeirc.name:
|
||||||
|
# Don't process our own stuff...
|
||||||
|
continue
|
||||||
|
log.debug('Processing link %s (homeirc=%s)', link, homeirc.name)
|
||||||
|
try:
|
||||||
|
linkednet = utils.networkobjects[network]
|
||||||
|
except KeyError:
|
||||||
|
# Network isn't connected yet.
|
||||||
|
continue
|
||||||
|
# Iterate through each of these links' channels' users
|
||||||
|
for user in linkednet.channels[channel].users.copy():
|
||||||
|
log.debug('Processing user %s/%s (homeirc=%s)', user, linkednet.name, homeirc.name)
|
||||||
|
if user == linkednet.pseudoclient.uid:
|
||||||
|
# We don't need to clone the PyLink pseudoclient... That's
|
||||||
|
# meaningless.
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if linkednet.users[user].remote:
|
||||||
|
# Is the .remote atrribute set? If so, don't relay already
|
||||||
|
# relayed clients; that'll trigger an endless loop!
|
||||||
|
continue
|
||||||
|
except AttributeError: # Nope, it isn't.
|
||||||
|
pass
|
||||||
|
userobj = linkednet.users[user]
|
||||||
|
userpair_index = relayusers.get((linkednet.name, user))
|
||||||
|
ident = userobj.ident
|
||||||
|
host = userobj.host
|
||||||
|
realname = userobj.realname
|
||||||
|
# And a third for loop to spawn+join pseudoclients for
|
||||||
|
# them all.
|
||||||
|
log.debug('Okay, spawning %s/%s everywhere', user, linkednet.name)
|
||||||
|
for name, irc in utils.networkobjects.items():
|
||||||
|
nick = normalizeNick(irc, linkednet.name, userobj.nick)
|
||||||
|
if name == linkednet.name:
|
||||||
|
# Don't relay things to their source network...
|
||||||
|
continue
|
||||||
|
# If the user (stored here as {(netname, UID):
|
||||||
|
# {network1: UID1, network2: UID2}}) exists, don't spawn it
|
||||||
|
# again!
|
||||||
|
u = None
|
||||||
|
if userpair_index is not None:
|
||||||
|
u = userpair_index.get(irc.name)
|
||||||
|
if u is None: # .get() returns None if not found
|
||||||
|
u = irc.proto.spawnClient(irc, nick, ident=ident,
|
||||||
|
host=host, realname=realname).uid
|
||||||
|
irc.users[u].remote = linkednet.name
|
||||||
|
log.debug('(%s) Spawning client %s (UID=%s)', irc.name, nick, u)
|
||||||
|
relayusers[(linkednet.name, userobj.uid)][irc.name] = u
|
||||||
|
irc.proto.joinClient(irc, u, channel)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def removeChannel(irc, channel):
|
||||||
if channel not in map(str.lower, irc.serverdata['channels']):
|
if channel not in map(str.lower, irc.serverdata['channels']):
|
||||||
irc.proto.partClient(irc, irc.pseudoclient.uid, channel)
|
irc.proto.partClient(irc, irc.pseudoclient.uid, channel)
|
||||||
|
|
||||||
|
def relay(homeirc, func, args):
|
||||||
|
"""<source IRC network object> <function name> <args>
|
||||||
|
|
||||||
|
Relays a call to <function name>(<args>) to every IRC object's protocol
|
||||||
|
module except the source IRC network's."""
|
||||||
|
for name, irc in utils.networkobjects.items():
|
||||||
|
if name == homeirc.name:
|
||||||
|
continue
|
||||||
|
f = getattr(irc.proto, func)
|
||||||
|
f(*args)
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def create(irc, source, args):
|
def create(irc, source, args):
|
||||||
"""<channel>
|
"""<channel>
|
||||||
@ -95,8 +221,9 @@ def create(irc, source, args):
|
|||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
||||||
initializechannel(irc, channel)
|
initializeChannel(irc, channel)
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
|
utils.add_hook(handle_join, 'JOIN')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def destroy(irc, source, args):
|
def destroy(irc, source, args):
|
||||||
@ -117,7 +244,7 @@ def destroy(irc, source, args):
|
|||||||
|
|
||||||
if (irc.name, channel) in db:
|
if (irc.name, channel) in db:
|
||||||
del db[(irc.name, channel)]
|
del db[(irc.name, channel)]
|
||||||
removechannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)
|
utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)
|
||||||
@ -167,7 +294,7 @@ def link(irc, source, args):
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
entry['links'].add((irc.name, localchan))
|
entry['links'].add((irc.name, localchan))
|
||||||
initializechannel(irc, localchan)
|
initializeChannel(irc, localchan)
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
@ -206,41 +333,36 @@ def delink(irc, source, args):
|
|||||||
for link in entry['links'].copy():
|
for link in entry['links'].copy():
|
||||||
if link[0] == remotenet:
|
if link[0] == remotenet:
|
||||||
entry['links'].remove(link)
|
entry['links'].remove(link)
|
||||||
removechannel(utils.networkobjects[remotenet], link[1])
|
removeChannel(utils.networkobjects[remotenet], link[1])
|
||||||
else:
|
else:
|
||||||
entry['links'].remove((irc.name, channel))
|
entry['links'].remove((irc.name, channel))
|
||||||
removechannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
|
|
||||||
def relay(homeirc, func, args):
|
def main():
|
||||||
"""<source IRC network object> <function name> <args>
|
|
||||||
|
|
||||||
Relays a call to <function name>(<args>) to every IRC object's protocol
|
|
||||||
module except the source IRC network's."""
|
|
||||||
for name, irc in utils.networkobjects.items():
|
|
||||||
if name == homeirc.name:
|
|
||||||
continue
|
|
||||||
f = getattr(irc.proto, func)
|
|
||||||
f(*args)
|
|
||||||
|
|
||||||
def main(irc):
|
|
||||||
loadDB()
|
loadDB()
|
||||||
# HACK: we only want to schedule this once globally, because
|
utils.schedulers['relaydb'] = scheduler = sched.scheduler()
|
||||||
# exportDB will otherwise be called by every network that loads this
|
scheduler.enter(30, 1, exportDB, argument=(scheduler,))
|
||||||
# plugin.
|
# Thread this because exportDB() queues itself as part of its
|
||||||
if 'relaydb' not in utils.schedulers:
|
# execution, in order to get a repeating loop.
|
||||||
utils.schedulers['relaydb'] = scheduler = sched.scheduler()
|
thread = threading.Thread(target=scheduler.run)
|
||||||
scheduler.enter(30, 1, exportDB, argument=(scheduler,))
|
thread.daemon = True
|
||||||
# Thread this because exportDB() queues itself as part of its
|
thread.start()
|
||||||
# execution, in order to get a repeating loop.
|
'''
|
||||||
thread = threading.Thread(target=scheduler.run)
|
# Same goes for all the other initialization stuff; we only
|
||||||
thread.start()
|
# want it to happen once.
|
||||||
|
for network, ircobj in utils.networkobjects.items():
|
||||||
|
if ircobj.name != irc.name:
|
||||||
|
irc.proto.spawnServer(irc, '%s.relay' % network)
|
||||||
|
'''
|
||||||
|
|
||||||
for chanpair, entrydata in db.items():
|
for chanpair, entrydata in db.items():
|
||||||
network, channel = chanpair
|
network, channel = chanpair
|
||||||
initializechannel(utils.networkobjects[network], channel)
|
try:
|
||||||
for link in entrydata['links']:
|
initializeChannel(utils.networkobjects[network], channel)
|
||||||
network, channel = link
|
for link in entrydata['links']:
|
||||||
initializechannel(utils.networkobjects[network], channel)
|
network, channel = link
|
||||||
for network, ircobj in utils.networkobjects.items():
|
initializeChannel(utils.networkobjects[network], channel)
|
||||||
if ircobj.name != irc.name:
|
except KeyError:
|
||||||
irc.proto.spawnServer(irc, '%s.relay' % network)
|
pass # FIXME: initialize as soon as the network connects,
|
||||||
|
# not when the next JOIN occurs
|
||||||
|
Loading…
Reference in New Issue
Block a user